UI テストを作成する

完了

このセクションでは、Amita が説明した UI の動作を検証する Selenium テストを、Andy と Amita が作成するのを支援します。

Amita は、普段は Chrome、Firefox、および Microsoft Edge でテストを実行しています。 ここでは、同じことをします。 使用する Microsoft ホステッド エージェントは、これらの各ブラウザーで動作するように事前に構成されています。

GitHub からブランチをフェッチする

このセクションでは、GitHub から selenium ブランチをフェッチします。 その後、そのブランチを "チェックアウト" するか、そのブランチに切り替えます。 ブランチの内容は、Andy と Amita が作成したテストに沿って進めるのに役立ちます。

このブランチには、前のモジュールで作業した Space Game プロジェクトが含まれています。 また、最初に使用する Azure Pipelines 構成も含まれています。

  1. Visual Studio Code で、統合ターミナルを開きます。

  2. Microsoft のリポジトリから selenium という名前のブランチをダウンロードするには、そのブランチに切り替えて、次の git fetch および git checkout コマンドを実行します。

    git fetch upstream selenium
    git checkout -B selenium upstream/selenium
    

    ヒント

    前のユニットで Amita の手動テストに従った場合は、これらのコマンドを既に実行している可能性があります。 前のユニットで既に実行した場合でも、ここでもう一度実行できます。

    upstream とは Microsoft GitHub リポジトリを指していることを思い出してください。 その関係をセットアップしたため、プロジェクトの Git 構成ではアップストリーム リモートが認識されます。 これは、Microsoft のリポジトリからプロジェクトをフォークしてローカルにクローンしたときに設定しました。

    すぐに、origin と呼ばれる独自の GitHub リポジトリにこのブランチをプッシュします。

  3. 必要に応じて、Visual Studio Code で、azure-pipelines.yml ファイルを開きます。 初期構成について理解します。

    この構成は、このラーニング パスの以前のモジュールで作成したものに似ています。 アプリケーションのリリース構成のみをビルドします。 簡潔にするために、以前のモジュールで設定したトリガー、手動の承認、およびテストは省略されています。

    Note

    より堅牢な構成では、ビルド プロセスに参加するブランチを指定できます。 たとえば、コードの品質を検証するために、いずれかのブランチに変更をプッシュするたびに、単体テストを実行できます。 さらに包括的なテストを実行する環境にアプリケーションをデプロイすることもできます。 ただし、このデプロイは、pull request がある場合、リリース候補がある場合、またはコードを main にマージする場合にのみ実行します。

    詳細については、Git と GitHub を使用したビルド パイプラインでのコード ワークフローの実装に関するモジュール、およびビルド パイプラインのトリガーに関するページを参照してください。

単体テストのコードを記述する

Amita は、Web ブラウザーを制御するコードの記述方法を学ぶことにワクワクしています。

彼女と Andy は協力して Selenium テストを作成します。 Andy は既に空の NUnit プロジェクトを設定済みです。 このプロセス全体で、彼らは Selenium のドキュメント、いくつかのオンライン チュートリアル、および Amita が手動でテストを行ったときに取ったメモを参照します。 このモジュールの最後に、プロセスを進めるのに役立つその他のリソースが用意されています。

Andy と Amita がテストを作成するために使用するプロセスを確認してみましょう。 Visual Studio Code で、Tailspin.SpaceGame.Web.UITests ディレクトリにある HomePageTest.cs を開くことで、進めていくことができます。

HomePageTest クラスを定義する

Andy: 最初に行う必要があるのは、テスト クラスを定義することです。 いくつかの名前付け規則のいずれかに従うことを選択できます。 クラス HomePageTest を呼び出しましょう。 このクラスでは、ホーム ページに関連するすべてのテストを行います。

Andy は、次のコードを HomePageTest.cs に追加します。

public class HomePageTest
{
}

Andy: このクラスに public とマークして、NUnit フレームワークで使用できるようにする必要があります。

IWebDriver メンバー変数を追加する

Andy: 次に、IWebDriver メンバー変数が必要です。 IWebDriver は、Web ブラウザーを起動し、Web ページのコンテンツを操作するために使用するプログラミング インターフェイスです。

Amita: プログラミングのインターフェイスについては聞いたことがあります。 もっと詳しく教えてください。

Andy: インターフェイスは、コンポーネントの動作を示す仕様またはブループリントと考えることができます。 インターフェイスは、そのコンポーネントのメソッド、つまり動作を提供します。 ただし、このインターフェイスは基になる詳細情報を提供しません。 あなたか他の誰かが、そのインターフェイスを実装する 1 つ以上の "具象クラス" を作成します。 Selenium が、私たちに必要な具象クラスを提供します。

次の図は、IWebDriver インターフェイスと、このインターフェイスを実装するいくつかのクラスを示しています。

Diagram of the IWebDriver interface, its methods, and concrete classes.

図には IWebDriver で提供される 3 つのメソッド (NavigateFindElement、および Close) が示されています。

ここに示されている 3 つのクラス、ChromeDriverFirefoxDriver、および EdgeDriver のそれぞれが、IWebDriver とそのメソッドを実装します。 IWebDriver を実行するその他のクラス (SafariDriver など) もあります。 各ドライバー クラスは、それが表す Web ブラウザーを制御できます。

Andy は、次のコードのように、driver という名前のメンバー変数を HomePageTest クラスに追加します。

public class HomePageTest
{
    private IWebDriver driver;
}

テスト フィクスチャを定義する

Andy: Chrome、Firefox、および Edge でテストのセット全体を実行したいと思います。 NUnit では、"テスト フィクスチャ" を使用して、テスト対象のブラウザーごとに 1 回ずつ、テストのセット全体を複数回実行できます。

NUnit で、TestFixture 属性を使用してテスト フィクスチャを定義します。 Andy は、次の 3 つのテスト フィクスチャを HomePageTest クラスに追加します。

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private IWebDriver driver;
}

Andy: 次に、テスト クラスのコンストラクターを定義する必要があります。 このコンストラクターは、NUnit でこのクラスのインスタンスが作成されるときに呼び出されます。 コンストラクターは、その引数として、テスト フィクスチャにアタッチされた文字列を受け取ります。 コードは次のようになります。

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private string browser;
    private IWebDriver driver;

    public HomePageTest(string browser)
    {
        this.browser = browser;
    }
}

Andy:browser メンバー変数を追加して、設定コードで現在のブラウザー名を使用できるようにします。 次に、設定コードを記述しましょう。

Setup メソッドを定義する

Andy: 次に、テスト対象のブラウザー用としてこのインターフェイスを実装するクラス インスタンスに IWebDriver メンバー変数を割り当てる必要があります。 ChromeDriverFirefoxDriver、および EdgeDriver の各クラスは、それぞれ Chrome、Firefox、および Edge 用にこのインターフェイスを実装します。

driver 変数を設定する Setup という名前のメソッドを作成してみましょう。 OneTimeSetUp 属性を使用して、このメソッドをテスト フィクスチャごとに 1 回実行するように NUnit に指示します。

[OneTimeSetUp]
public void Setup()
{
}

Setup メソッドでは、switch ステートメントを使用して、ブラウザー名に基づいて、適切な具象実装に driver メンバー変数を割り当てることができます。 そのコードをここで追加しましょう。

// Create the driver for the current browser.
switch(browser)
{
    case "Chrome":
    driver = new ChromeDriver(
        Environment.GetEnvironmentVariable("ChromeWebDriver")
    );
    break;
    case "Firefox":
    driver = new FirefoxDriver(
        Environment.GetEnvironmentVariable("GeckoWebDriver")
    );
    break;
    case "Edge":
    driver = new EdgeDriver(
        Environment.GetEnvironmentVariable("EdgeWebDriver"),
        new EdgeOptions
        {
            UseChromium = true
        }
    );
    break;
    default:
    throw new ArgumentException($"'{browser}': Unknown browser");
}

各ドライバー クラスのコンストラクターは、Selenium で Web ブラウザーを制御するために必要なドライバー ソフトウェアへの省略可能なパスを取ります。 後で、ここに示されている環境変数の役割について説明します。

この例では、EdgeDriver コンストラクターに、Chromium バージョンの Edge を使用することを指定するための追加オプションも必要です。

ヘルパー メソッドを定義する

Andy: テスト全体で次の 2 つのアクションを繰り返す必要があることがわかっています。

  • クリックするリンクや、表示されると想定されているモーダル ウィンドウなど、ページの要素を見つける
  • モーダル ウィンドウを表示するリンクや、各モーダルを閉じるボタンなど、ページ上の要素をクリックする

アクションごとに 1 つずつ、2 つのヘルパー メソッドを記述してみましょう。 まず、ページ上の要素を検索するメソッドから始めます。

FindElement ヘルパー メソッドを記述する

ページ上の要素を検索するときは、通常、ページの読み込みやユーザーによる情報の入力などの他のイベントの発生時です。 Selenium には、条件が true になるまで待機できるようにする WebDriverWait クラスが用意されています。 指定された期間内で条件が true でない場合、WebDriverWait は例外またはエラーをスローします。 WebDriverWait クラスを使用して、特定の要素が表示され、ユーザー入力を受け取る準備が整うまで待機することができます。

ページ上の要素を検索するには、By クラスを使用します。 By クラスには、要素を、その名前、CSS クラス名、HTML タグ、または id 属性によって検索できるメソッドが用意されています。

Andy と Amita は、FindElement ヘルパー メソッドをコーディングしました。 次のコードのようになります。

private IWebElement FindElement(By locator, IWebElement parent = null, int timeoutSeconds = 10)
{
    // WebDriverWait enables us to wait for the specified condition to be true
    // within a given time period.
    return new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds))
        .Until(c => {
            IWebElement element = null;
            // If a parent was provided, find its child element.
            if (parent != null)
            {
                element = parent.FindElement(locator);
            }
            // Otherwise, locate the element from the root of the DOM.
            else
            {
                element = driver.FindElement(locator);
            }
            // Return true after the element is displayed and is able to receive user input.
            return (element != null && element.Displayed && element.Enabled) ? element : null;
        });
}

ClickElement ヘルパー メソッドを記述する

Andy: 次に、リンクをクリックするためのヘルパー メソッドを記述しましょう。 Selenium には、そのメソッドを記述するいくつかの方法が用意されています。 そのうちの 1 つは IJavaScriptExecutor インターフェイスです。 これにより、JavaScript を使用して、プログラムでリンクをクリックすることができます。 この方法は、最初にビュー内にスクロールしなくてもリンクをクリックできるため、うまく機能します。

ChromeDriverFirefoxDriver、および EdgeDriver のそれぞれは、IJavaScriptExecutor を実装します。 このインターフェイスにドライバーをキャストしてから、ExecuteScript を呼び出して、基になる HTML オブジェクトで JavaScript の click() メソッドを実行する必要があります。

Andy と Amita は、ClickElement ヘルパー メソッドをコーディングしました。 次のコードのようになります。

private void ClickElement(IWebElement element)
{
    // We expect the driver to implement IJavaScriptExecutor.
    // IJavaScriptExecutor enables us to execute JavaScript code during the tests.
    IJavaScriptExecutor js = driver as IJavaScriptExecutor;

    // Through JavaScript, run the click() method on the underlying HTML object.
    js.ExecuteScript("arguments[0].click();", element);
}

Amita: これらのヘルパー メソッドを追加するというアイデアが好きです。 ほぼすべてのテストで使用するのに十分一般的であるように見えます。 必要に応じて、後でヘルパー メソッドを追加できます。

テスト メソッドを定義する

Andy: これで、テスト メソッドを定義する準備ができました。 前に実行した手動テストに基づいて、このメソッド ClickLinkById_ShouldDisplayModalById を呼び出しましょう。 テスト メソッドには、このテストで何が達成されるかを正確に定義する、わかりやすい名前を付けることをお勧めします。 ここで、id 属性で定義されているリンクをクリックします。 次に、id 属性も使用して、適切なモーダル ウィンドウが表示されることを確認します。

Andy は、テスト メソッド用のスタート コードを追加します。

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
}

Andy: コードを追加する前に、このテストで実行する必要があることを定義しましょう。

Amita: 私はこの部分を処理できます。 次の目的があります。

  1. id 属性でリンクを見つけて、リンクをクリックします。
  2. 結果として得られるモーダルを見つけます。
  3. モーダルを閉じます。
  4. モーダルが正常に表示されたことを確認します。

Andy: すばらしい。 他にもいくつか処理を行う必要があります。 たとえば、ドライバーを読み込むことができなかった場合にはテストを無視する必要があり、モーダルが正常に表示された場合にのみモーダルを閉じる必要があります。

コーヒーのおかわりを入れた後、Andy と Amita はテスト メソッドにコードを追加します。 彼らは、記述したヘルパー メソッドを使用してページ要素を検索し、リンクとボタンをクリックします。 結果は次のとおりです。

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
    // Skip the test if the driver could not be loaded.
    // This happens when the underlying browser is not installed.
    if (driver == null)
    {
        Assert.Ignore();
        return;
    }

    // Locate the link by its ID and then click the link.
    ClickElement(FindElement(By.Id(linkId)));

    // Locate the resulting modal.
    IWebElement modal = FindElement(By.Id(modalId));

    // Record whether the modal was successfully displayed.
    bool modalWasDisplayed = (modal != null && modal.Displayed);

    // Close the modal if it was displayed.
    if (modalWasDisplayed)
    {
        // Click the close button that's part of the modal.
        ClickElement(FindElement(By.ClassName("close"), modal));

        // Wait for the modal to close and for the main page to again be clickable.
        FindElement(By.TagName("body"));
    }

    // Assert that the modal was displayed successfully.
    // If it wasn't, this test will be recorded as failed.
    Assert.That(modalWasDisplayed, Is.True);
}

Amita: これまでのところ、このコーディングはすばらしいものです。 しかし、このテストを以前に収集した id 属性に接続するにはどうすればよいでしょうか?

Andy: 良い質問ですね。 次にそれを処理します。

テスト ケースのデータを定義する

Andy: NUnit では、いくつかの方法でテストにデータを提供できます。 ここでは、TestCase 属性を使用します。 この属性は、後で実行時にテスト メソッドに返す引数を取ります。 それぞれがアプリのさまざまな機能をテストする、複数の TestCase 属性を使用できます。 各 TestCase 属性は、パイプラインの実行の最後に表示されるレポートに含まれるテスト ケースを生成します。

Andy は、これらの TestCase 属性をテスト メソッドに追加します。 これらの属性は、[Download game] ボタン、1 つのゲーム画面、およびランキングのトップ プレーヤーを記述します。 各属性は 2 つの id 属性を指定します。1 つはクリックするリンク用、もう 1 つは対応するモーダル ウィンドウ用です。

// Download game
[TestCase("download-btn", "pretend-modal")]
// Screen image
[TestCase("screen-01", "screen-modal")]
// // Top player on the leaderboard
[TestCase("profile-1", "profile-modal-1")]
public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{

...

Andy:TestCase 属性の最初のパラメーターは、クリックするリンク用の id 属性です。 2 番目のパラメーターは、表示されると予想されるモーダル ウィンドウ用の id 属性です。 これらのパラメーターが、テスト メソッドの 2 つの文字列引数にどのように対応しているかを確認できます。

Amita: それがわかります。 少し練習すれば、自分でテストを追加できると思います。 これらのテストがパイプラインで実行されているのをいつ確認できますか?

Andy: パイプラインを通じて変更内容をプッシュする前に、まず、コードがコンパイルされ、ローカルで実行されることを確認しましょう。 変更内容を GitHub にコミットしてプッシュし、すべてが正しく動作することを確認した後にのみ、それらがパイプラインを通過することが確認されます。 テストをローカルで実行してみましょう。