テストをしてみよう（結合テスト・シナリオテスト）

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

# 概要
ここでは結合テストを行います。
結合テストとは、プログラム単体のテストではなく、一連の流れでシステム全体が正常に動作するかテストを行うものです。
シナリオを作成し、その手順で手作業でブラウザ操作を行う方法もありますが、今回はseleniumというツールを使ってテストを自動化していきます。
これも自動化しておくことで、システムに改修が加わるたびにテスト実行が簡単にでき素早くテストを実行できるようになります。

https://www.selenium.dev/ja/

# パッケージのインストール
selenium-webdriverはWebブラウザをプログラムから自動的に操作するためのツールです。
ChromeDriverはChromeブラウザをプログラムで動かす為のドライバーです。
この２つを使うことでSeleniumでChromeブラウザを自動操作したテストが実行できるようになります。
ターミナルを開いてPCにインストールします。

```bash
cd ~/Desktop/website #フォルダ内に移動
npm i selenium-webdriver
npm i chromedriver
```


# テストのシナリオを考える
どのような操作のテストを行うのかテストのシナリオを考えます。
今回は、Homeページにアクセスしたときと、お問合わせページから申し込みを実行した場合のテストを考えます。
こちらも単体テストと同様に想定される全てのパターンのシナリオを作成します。
*※シナリオ動作はそれぞれ複数有りますが、jestでテストしている内容は★1〜5の部分となります。*

- Homeページ

| No. | 内容|
|--:|:--|
| 1 | google chromeブラウザを立ち上げてサイトにアクセス |
| 2 |  titleが期待の文字列「WebSite」と一致するかチェック  **★1**  |

- お問合わせページ（正常系）

| No. | 内容|
|--:|:--|
| 1 | google chromeブラウザを立ち上げてサイトにアクセス |
| 2 | titleが期待の文字列「WebSite\|お問合わせ」と一致するかチェック　**★2** |
| 3 | お名前の入力フォームに「テスト太郎」と入力 |
| 4 | Emailの入力フォームに「test@test.local」と入力 |
| 5 | bodyの入力フォームに「hogehoge」と入力 |
| 6 | 送信ボタンをクリック |
| 7 | alertのメッセージ取得 |
| 8 | alertの文字列が期待の文字列「success!」と一致するかチェック **★3** |
| 9 | alertを閉じる |

- お問合わせページ（異常系：name不正）

| No. | 内容|
|--:|:--|
| 1 | google chromeブラウザを立ち上げてサイトにアクセス |
| 2 | titleが期待の文字列「WebSite\|お問合わせ」と一致するかチェック **★4** |
| 3 | お名前の入力フォームを未入力 |
| 4 | Emailの入力フォームに「test@test.local」と入力 |
| 5 | bodyの入力フォームに「hogehoge」と入力 |
| 6 | 送信ボタンをクリック |
| 7 | バリデーションのエラーメッセージが表示されているかチェック  **★5**|


# テストコード作成
__tests__フォルダ内にui.jsを作成します。
まず、selenium-webdriverの利用する機能の宣言をします。
ここでは、ドライバー作成してビルドするbuilder、フォームタグのIdを指定するときに利用するBy、アラートが表示するまで待つために利用するuntil機能の３つを利用するので宣言します。
seleniumではテスト実行タイムアウト時間が5000msで設定されているのですが、今回は5000msだとタイムアウトしてしまう可能性があるので10000msを設定しておきます。

```__tests__/ui.js
const { Builder, By, until } = require("selenium-webdriver");
require("chromedriver");

//基本タイムアウト時間が5000なので長めに設定
jest.setTimeout(10000);
```
## Homeページ
シナリオに沿って動作を記述していきます。
>URLは自身のものに修正してください。

```__tests__/ui.js
//HOMEのテスト
it("HOMEページタイトル", async () => {

    //chromeを取得
    const driver = await new Builder().forBrowser("chrome").build();
    //ページに移動
    await driver.get("https://xxxxxxxxxxxxx.on.drv.tw/website");
    //タイトル取得
    const title = await driver.getTitle();
    //評価（titleが期待の文字列「WebSite」と一致するかチェック）
    expect(title).toBe("WebSite");
    //終了
    await driver.quit();

});
```
## お問合わせページ（正常系）
Homeページテストの下に記述します。

```__tests__/ui.js
it("お問合わせページ正常系", async () => {

    //chromeを取得
    const driver = await new Builder().forBrowser("chrome").build();
    //ページに移動
    await driver.get("https://xxxxxxxxxxxxx.on.drv.tw/website/contact.html");
    //タイトル取得
    const title = await driver.getTitle();
    //タイトル評価
    expect(title).toBe("WebSite|お問合わせ");
    //formの操作
    //name取得し入力
    const contact_name = await driver.findElement(By.id("name"));
    contact_name.sendKeys("テスト太郎");
    //email取得し入力
    const email = await driver.findElement(By.id("email"));
    email.sendKeys("test@test.local");
    //body取得し入力
    const body = await driver.findElement(By.id("body"));
    body.sendKeys("hogehoge");
    //btn取得とクリック
    const submit_btn = await driver.findElement(By.id("submit_btn"));
    submit_btn.click();
    //alertが出るまで待つ
    await driver.wait(until.alertIsPresent());
    //アラートに移動
    const alert = await driver.switchTo().alert();
    //アラートメッセージ取得
    const alertText = await alert.getText();
    //評価
    expect(alertText).toBe("success!");
    //alertのOKを押す
    await alert.accept();
    //終了
    await driver.quit();

});
```

## お問合わせページ異常系：name不正
正常系をコピーして、nameの取得と入力を削除、アラートの期待値を「validation error!」にします。

```__tests__/ui.js
//Contactのテスト
it("お問合わせページ異常系：name不正", async () => {

    //chromeを取得
    const driver = await new Builder().forBrowser("chrome").build();
    //ページに移動
    await driver.get("https://xxxxxxxxxxxxx.on.drv.tw/website/contact.html");
    //タイトル取得
    const title = await driver.getTitle();
    //タイトル評価
    expect(title).toBe("WebSite|お問合わせ");
    //formの操作
    ///////////nameは未入力//////////////
    //email取得し入力
    const email = await driver.findElement(By.id("email"));
    email.sendKeys("test@test.local");
    //body取得し入力
    const body = await driver.findElement(By.id("body"));
    body.sendKeys("hogehoge");
    //btn取得とクリック
    const submit_btn = await driver.findElement(By.id("submit_btn"));
    submit_btn.click();
    //エラーメッセージが表示されているかチェック（表示されていたらtrueが返る）
    const name_error = await driver.findElement(By.id("name_error")).isDisplayed();
    expect(name_error).toBe(true);
    //終了
    await driver.quit();

});

```
# テスト実行

保存したらテストを実行します。
jestはファイル名を指定して実行できます。（指定しないと.test.jsファイルの全てを実行します）

```
npx jest ui.js
```

>もしエラーが出たらChromeを最新のバージョンにバージョンアップしてみてください。

テストが始まると自動でブラウザが立ち上がりシナリオ通りに動作しているのがわかります。
完了するとテスト結果が表示されます。

<img width="600" alt="000001.jpg" src="https://drive.google.com/uc?export=view&id=1zjvYbCXv0BEtQLvaCWNPzQkzuM9-NuV1">

# 最終的なコード

```__tests__/ui.js
const { Builder, By, until } = require("selenium-webdriver");
require("chromedriver");

//基本タイムアウト時間が5000なので長めに設定
jest.setTimeout(10000);

//HOMEのテスト
it("HOMEページタイトル", async () => {

    //chromeを取得
    const driver = await new Builder().forBrowser("chrome").build();
    //ページに移動
    await driver.get("https://xxxxxxxxxxx.on.drv.tw/website/");
    //タイトル取得
    const title = await driver.getTitle();
    //評価（titleが期待の文字列「WebSite」と一致するかチェック）
    expect(title).toBe("WebSite");
    //終了
    await driver.quit();

});

it("お問合わせページ正常系", async () => {

    //chromeを取得
    const driver = await new Builder().forBrowser("chrome").build();
    //ページに移動
    await driver.get("https://xxxxxxxxxxx.on.drv.tw/website/contact.html");
    //タイトル取得
    const title = await driver.getTitle();
    //タイトル評価
    expect(title).toBe("WebSite|お問合わせ");
    //formの操作
    //name取得し入力
    const contact_name = await driver.findElement(By.id("name"));
    contact_name.sendKeys("テスト太郎");
    //email取得し入力
    const email = await driver.findElement(By.id("email"));
    email.sendKeys("test@test.local");
    //body取得し入力
    const body = await driver.findElement(By.id("body"));
    body.sendKeys("hogehoge");
    //btn取得とクリック
    const submit_btn = await driver.findElement(By.id("submit_btn"));
    submit_btn.click();
    //alertが出るまで待つ
    await driver.wait(until.alertIsPresent());
    //アラートに移動
    const alert = await driver.switchTo().alert();
    //アラートメッセージ取得
    const alertText = await alert.getText();
    //評価
    expect(alertText).toBe("success!");
    //alertのOKを押す
    await alert.accept();
    //終了
    await driver.quit();

});

//Contactのテスト
it("お問合わせページ異常系：name不正", async () => {

    //chromeを取得
    const driver = await new Builder().forBrowser("chrome").build();
    //ページに移動
    await driver.get("https://xxxxxxxxxxx.on.drv.tw/website/contact.html");
    //タイトル取得
    const title = await driver.getTitle();
    //タイトル評価
    expect(title).toBe("WebSite|お問合わせ");
    //formの操作
    ///////////nameは未入力//////////////
    //email取得し入力
    const email = await driver.findElement(By.id("email"));
    email.sendKeys("test@test.local");
    //body取得し入力
    const body = await driver.findElement(By.id("body"));
    body.sendKeys("hogehoge");
    //btn取得とクリック
    const submit_btn = await driver.findElement(By.id("submit_btn"));
    submit_btn.click();
    //エラーメッセージが表示されているかチェック（表示されていたらtrueが返る）
    const name_error = await driver.findElement(By.id("name_error")).isDisplayed();
    expect(name_error).toBe(true);
    //終了
    await driver.quit();

});

```


# まとめ

* 結合テストとは、別々に動作するシステム間を連携させて行うテスト
  * 結合テストに厳密な定義があるわけではなく、システムによっては総合テストやシステムテストと同義
* 単体テスト同様自動化することも可能
  * 但し、最初の何回かは手作業で行うことが一般的（たぶん）
* 通常は数十〜数百のシナリオを実行するのでそれなりの工数がかかる

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

* 結合テストの定義、実施の有無、実行内容をどこにどう記述すべきか？
* 結合テストをあわせ、テストの内容が妥当（十分）であることをどう証明するか？

# 関連コンテンツ

* 次：[テストをしてみよう（性能テスト）](https://qiita.com/minimumskills/items/f5bab03b081740c0b369)
* 前：[テストをしてみよう（単体テスト）](https://qiita.com/minimumskills/items/661e5287681469e29546)