テストをしてみよう（性能テスト）

この記事は、「[架空プロジェクトを通してシステム開発とドキュメント作成を体験してみる（2022 Late）](https://qiita.com/minimumskills/items/aedf6a93658db3f74747)」の記事の一部です。

# 概要
ここではパフォーマンステストを行っていきます。
通常パフォーマンステストを実行する場合、[JMeter](https://jmeter.apache.org/)など負荷テストツールを利用して実行しますが、今回はテストするAPIがGASで実装されているため、独自のテストスクリプトを記述してテストを実行してみたいと思います。（JMeterを使って不用意にパフォーマンステストを実行するとGoogleに攻撃とみなされてブロックされてしまう等のリスクがあるため）

今回はレスポンス時間の測定を行うコードをJavaScriptで記述してnodeで実行してみます。
以下の2項目の測定を行います。

- webのトップページのレスポンス速度
- APIに対するレスポンス速度（お問合わせ部分に関しては、APIのレスポンス時間が重要となるため）

<img width="400" alt="000001.jpg" src="https://drive.google.com/uc?export=view&id=10U6j11FC2Z5tgjR1fnubpVAd29_AJqfL">


アクセス回数は10回を設定し、それぞれのレスポンス時間、10回のレスポンス時間の合計、平均レスポンス時間を測定してみます。レスポンスが正常に返ってきているかどうか確認するために、apiレスポンスをそれぞれコンソールに出力して確認します。

# webサイトパフォーマンス

## ファイルの作成

__tests__フォルダがありますが、この中に一緒に作成するとjestを実行したときに一緒に実行しようとしてエラーになるため、別にtestsというフォルダを作成します。そして、その中に、「web_performance.js」という名前のファイルを作成します。

<img width="400" alt="000010.jpg" src="https://drive.google.com/uc?export=view&id=1JJV8hpyBlDgyktroHQvtyNgy3jxz8INP">

## 実装

Nodeでもブラウザと同じようにfetchメソッドを使えるようにするnode-fetchをインポートします。
速度測定はNode.jsに標準で入っているpref_hooksモジュールで行えます。

### 必要なnodeパッケージのインストール

コンソールを開きtestsフォルダの中で以下のコマンドを実行します。

```bash
cd tests
npm init -y #初期化
npm i node-fetch
```

npm initするとpackage.jsonやnode_moduleが追加されます。
node-fetchをインストールすると、バージョン管理に使われるpackage-lock.jsonも追加されます。
<img width="400" alt="000020.jpg" src="https://drive.google.com/uc?export=view&id=1c7g-vWpoqmGaGuYx-2T84kLBJ30ahBS6">



node-fetchは最新バージョン（v3x）ではESモジュール専用となっていますが、Node.jsはデフォルトでCommonJS（関数を使ってモジュールを読み込む）となっているため、package.jsonでモジュールシステムを変えてあげます。

```diff_json:package.json
    {
      "name": "tests",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "directories": {
        "test": "tests"
      },
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "",
      "license": "ISC",
+     "type": "module",
       "dependencies": {
         "node-fetch": "^3.2.10"
       }
    }
```

### web_performance.js編集
#### fetch
node-fetchとperformance APIを呼び出すため、web_performance.jsに記載していきます。
> perf_hooksはnodeのビルトインモジュールとして標準で組み込まれているためinstallは不要です。
>ビルトインモジュールを確認したい場合は
` node -e "console.log(require('module').builtinModules);" `
をターミナルで実行して見てください。

```tests/web_performance.js
import fetch from 'node-fetch';
import { performance } from 'perf_hooks';
```

測定したいサイト（[DriveToWeb](https://www.drv.tw/)でトップページのURLを確認）のURLと、測定する回数を指定します。

```diff_javascript:tests/web_performance.js
    import fetch from 'node-fetch';
    import { performance } from 'perf_hooks';

+   //URL
+   const base_url = "https://xxxxxxxxxxx.on.drv.tw/website";
+
+   //各種変数
+   const loop = 10; //ループ回数（測定する回数）
```

```javascript:コピペ用
//URL
const base_url = "https://xxxxxxxxxxxxxxxxxxx.on.drv.tw/website";

//各種変数
const loop = 10; //ループ回数（測定する回数）
```
指定したループ回数分アクセスするようにfor文でループしてアクセスを実行します。
この処理では、fetchでHTTPレスポンスを取得し、その値をconsoleに出力します。そのため同期的に処理をするようにasync/awaitを使って記述をします。

```diff_javascript:tests/web_performance.js
    import fetch from 'node-fetch';
    import { performance } from 'perf_hooks';

    //URL
    const base_url = "https://xxxxxxxxxxxxxxxxxxx.on.drv.tw/website";

    //各種変数
    const loop = 10; //ループ回数（測定する回数）

+   //計測処理
+   (async () => {
+       //ループ
+       for (let i = 0; i < loop; i++) {
+
+           //リクエスト
+           const response = await fetch(base_url);
+           console.log(response.status); //コンソールに出力
+
+           
+       }
+   })();
```

```javascript:コピペ用
//計測処理
(async () => {
    //ループ
    for (let i = 0; i < loop; i++) {

        //リクエスト
        const response = await fetch(base_url);
        console.log(response.status); //コンソールに出力
        
    }
})();
```

この状態でコンソールから一度実行してみます。
実行は下記の通りです。
> websiteフォルダ直下にいる場合です。（位置確認コマンドはpwd）

```bash
node tests/web_performance.js
```
うまくいっている場合、ステータス「200」が10回返ってきます。
<img width="400" alt="000030.jpg" src="https://drive.google.com/uc?export=view&id=1WvgWTDWiAw2UigOSwe-22y88J4g2qbCJ">

#### 計測
時間を測定したいのでループ内で、1回ずつアクセス開始・終了時間をperformance.now()を使って取得します（リクエストの開始終了前後にperformance.now()を記述することで取得できます）。
取得した開始・終了時間からアクセス時間を計算します。
10回のアクセス時間を足して、ループの外でループ数で割って平均を出します。

```diff_javascript:tests/web_performance.js
    import fetch from 'node-fetch';
    import { performance } from 'perf_hooks';

    //URL
    const base_url = "https://xxxxxxxxxxxxxxxxxxx.on.drv.tw/website";

    //各種変数
    const loop = 10; //ループ回数（測定する回数）
+   let sumTime = 0; //合計時間

    //計測処理
    (async () => {
        //ループ
        for (let i = 0; i < loop; i++) {

+           //開始時間取得
+           const startTime = performance.now();

            //リクエスト
            const response = await fetch(base_url);

            console.log(response.status); //コンソールに出力

+           //終了時間取得
+           const endTime = performance.now();
+           // レスポンス時間計測（ミリ秒を秒に直す）
+           const responseTime = (endTime - startTime) / 1000;
+           //合計時間を合算
+           sumTime += responseTime;
+           //レスポンジ時間出力
+           console.log(responseTime);
        }
+       //合計出力
+       console.log("sum=" + sumTime);
+       //平均出力
+       console.log("ave=" + sumTime / loop);

    })();
```

```javascript:コピペ用
import fetch from 'node-fetch';
import { performance } from 'perf_hooks';

//URL
const base_url = "https://xxxxxxxxxxxxx.on.drv.tw/website";

//各種変数
const loop = 10; //ループ回数（測定する回数）
let sumTime = 0; //合計時間

//計測処理
(async () => {
    //ループ
    for (let i = 0; i < loop; i++) {

        //開始時間取得
        const startTime = performance.now();

        //リクエスト
        const response = await fetch(base_url);

        console.log(response.status); //コンソールに出力

        //終了時間取得
        const endTime = performance.now();
        // レスポンス時間計測（ミリ秒を秒に直す）
        const responseTime = (endTime - startTime) / 1000;
        //合計時間を合算
        sumTime += responseTime;
        //レスポンジ時間出力
        console.log(responseTime);
    }

    //合計出力
    console.log("sum=" + sumTime);
    //平均出力
    console.log("ave=" + sumTime / loop);

})();
```

保存して実行してみます。

```bash
node tests/web_performance.js
```
「200」の後にレスポンス時間が返ってきています。
sumは合計が表示されます。この場合「6.284秒」ということです。
一番最後は10回のレスポンス時間の平均が表示されます。
この場合平均レスポンスは「0.628秒」ということです。
<img width="400" alt="000040.jpg" src="https://drive.google.com/uc?export=view&id=18-Z3JaAWsUatz3ipZmP7F9bxUx2kyEir">


# APIパフォーマンス
testsの中に、「api_performance.js」という名前のファイルを作成します。

<img width="400" alt="000050.jpg" src="https://drive.google.com/uc?export=view&id=1SuQzIfIK8Ay_3409FG_evlE6dGUk7PJZ">



## 実装

### api_performance.js編集
#### fetch
インストールしたものを呼び出します。

```tests/api_performance.js
import fetch from 'node-fetch';
import { performance } from 'perf_hooks';
```

測定したいAPIのURLと、測定する回数を指定します。

```diff_javascript:tests/api_performance.js
    import fetch from 'node-fetch';
    import { performance } from 'perf_hooks';

+   //APIのURL
+   const base_url = "https://script.google.com/macros/s/{デプロイID}/exec";
+
+   //各種変数
+   const loop = 10; //ループ回数（測定する回数）
```

```javascript:コピペ用
//APIのURL
const base_url = "https://script.google.com/macros/s/{デプロイID}/exec";

//各種変数
const loop = 10; //ループ回数（測定する回数）
```
指定したループ回数分アクセスするようにfor文でループしてアクセスを実行します。
この処理では、fetchで取得した値をtextで返し、その値をconsoleに出力します。そのため同期的に処理をするようにasync/awaitを使って記述をします。

```diff_javascript:tests/api_performance.js
    import fetch from 'node-fetch';
    import { performance } from 'perf_hooks';

    //APIのURL
    const url = "https://script.google.com/macros/s/{デプロイID}/exec";

    //各種変数
    const loop = 10; //ループ回数（測定する回数）

+   //計測処理
+   (async () => {
+       //繰り返す
+       for (let i = 0; i < loop; i++) {
+           //リクエスト
+           const response = await fetch(url, {
+               method: "post",
+               headers: {
+                   "Content-Type": "application/x-www-form-urlencoded"
+               },
+               body: encodeURI(`name=test&email=test@tset.local&body=test`)
+           });
+           const json = await response.json(); //json返す
+           console.log(json.message); //コンソールに出力
+       }
+   })();
```

```javascript:コピペ用
//計測処理
(async () => {
    //ループ
    for (let i = 0; i < loop; i++) {
        //リクエスト
        const response = await fetch(base_url, {
            method: "post",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            },
            body: encodeURI(`name=test&email=test@tset.local&body=test`)
        });
        const json = await response.json(); //json返す
        console.log(json.message); //コンソールに出力
    }
})();
```
この状態でコンソールから一度実行してみます。
実行は下記の通りです。

```bash
 node tests/api_performance.js
```
うまくいくと「success!」が10回表示されているはずです。
<img width="400" alt="000060.jpg" src="https://drive.google.com/uc?export=view&id=11aeXQoreGCS6eEq5o2waZsAzznBiuq-Q">

#### 計測
時間を測定したいのでループ内で、1回ずつアクセス開始・終了時間をperformance.now()を使って取得します（リクエスの開始終了前後にperformance.now()を記述することで取得できます）。
取得した開始・終了時間からアクセス時間を計算します。
10回アクセス数足して、ループの外でループ数で割って平均を出します。

```diff_javascript:tests/api_performance.js
    import fetch from 'node-fetch';
    import { performance } from 'perf_hooks';

    //APIのURL
    const base_url = "https://script.google.com/macros/s/{デプロイID}kEb/exec";

    //各種変数
    const loop = 10; //ループ回数（測定する回数）
+   let sumTime = 0; //合計時間

    //計測処理
    (async () => {
        //ループ
        for (let i = 0; i < loop; i++) {

+           //開始時間取得
+           const startTime = performance.now();

            //リクエスト
            const response = await fetch(base_url, {
                method: "post",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded"
                },
                body: encodeURI(`name=test&email=test@tset.local&body=aaa&channel=performance-test`)
            });
            const json = await response.json(); //json返す
            console.log(json.message); //コンソールに出力
+           const endTime = performance.now();
+           //レスポンス時間計測（ミリ秒を秒に直す）
+           const responseTime = (endTime - startTime) / 1000;
+           //合計時間を合算
+           sumTime += responseTime;
+           //レスポンジ時間出力
+           console.log(responseTime);
        }
+       //合計出力
+       console.log("sum=" + sumTime);
+       //平均出力
+       console.log("ave=" + sumTime / loop);

    })();
```
```javascript:コピペ用
import fetch from 'node-fetch';
import { performance } from 'perf_hooks';

//URL
const base_url = "https://script.google.com/macros/s/{デプロイID}/exec";

//各種変数
const loop = 10; //ループ回数（測定する回数）
let sumTime = 0; //合計時間

//計測処理
(async () => {
    //ループ
    for (let i = 0; i < loop; i++) {

        //開始時間取得
        const startTime = performance.now();

        //リクエスト
        const response = await fetch(base_url, {
            method: "post",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            },
            body: encodeURI(`name=test&email=test@tset.local&body=aaa&channel=performance-test`)
        });
        const json = await response.json(); //json返す
        console.log(json.message); //コンソールに出力

        const endTime = performance.now();
        //レスポンス時間計測（ミリ秒を秒に直す）
        const responseTime = (endTime - startTime) / 1000;
        //合計時間を合算
        sumTime += responseTime;
        //レスポンジ時間出力
        console.log(responseTime);
    }
    //合計出力
    console.log("sum=" + sumTime);
    //平均出力
    console.log("ave=" + sumTime / loop);

})();
```

保存して実行してみます。

```bash
node tests/api_performance.js
```
「success！」の後に、レスポンス時間が返ってきています。
合計はsumに表示され、この場合「17.395秒」となります。
一番最後は10回のレスポンス時間の平均が表示されます。
この場合平均レスポンスは「1.739秒」ということです。
<img width="400" alt="000070.jpg" src="https://drive.google.com/uc?export=view&id=1tu6gt6FFGTfUsvl2sFT6wMC6ZyRl8khT">

# まとめ

* パフォーマンステストには一般にJMeterとかが使われる（ケースバイケース）
* クラウド環境によっては事前申請がいる場合もある（最近は減った）
* 場合により自分でスクリプトを開発することもある

# ドキュメント作成視点での考察

* パフォーマンステスト実施の有無、内容はどこにどう記述すべきか？
* 結果はどのように評価されるべきか？（どこにどう記述されるべきか）

# 関連コンテンツ

* 次：[テストをしてみよう（セキュリティーテスト）](https://qiita.com/minimumskills/items/bb4feb7445c866e6f464)
* 前：[テストをしてみよう（結合テスト・シナリオテスト）](https://qiita.com/minimumskills/items/7153b49cff01dd22d37a)