🧪

自動E2Eテストにおけるテスタビリティ

2023/05/07に公開

はじめに

自動テストにおけるテスタビリティ

テスタビリティとは、テスト容易性・テスト実行可能性ともいい、テストが実行しやすいか、必要なテストが十分実施できるか、を表すものである。テスタビリティが高いということは、必要なテストが実行しやすく、不具合が見つかりやすいということになる。一方、テスタビリティが低いと、そもそもテスト実行が不可能だったり、実現できたとしても膨大なコストが発生したり、または、限られた方法でしかテストができないために不具合検出率が低くなる、ということが起こる。これらは、自動テストに限らずすべてのテストに当てはまる。自動テストにおいては、これらに加えて、「テストコードの実装のしやすさや、テストコードのメンテナンスのしやすさ、テストコードの壊れにくさ」も含まれる。

自動E2Eテストを書く上で重要なファクターの一つが、操作や検証の対象となる要素を指定する「ロケーティング」である。常に一意であり、変更される可能性が少ないロケータを用いることが重要で、そのようなロケータを指定できることが自動テストにおけるテスタビリティの要因になる。

自動テストでの心構え

自動テスト、特にE2Eでの自動テストは、どのようにテスト実行されるべきかという話。

フロントエンドの自動E2EテストのライブラリであるPlaywrightの公式ドキュメントのBest PracticeのページにTesting philosophy(テストの哲学)という節がある。
そこにはTest user-visible behaviorとして以下のような記述がある。

Automated tests should verify that the application code works for the end users, and avoid relying on implementation details such as things which users will not typically use, see, or even know about such as the name of a function, whether something is an array, or the CSS class of some element. The end user will see or interact with what is rendered on the page, so your test should typically only see/interact with the same rendered output.

要約すると、「エンドユーザーは、ブラウザがレンダリングしたアプリケーションを見て操作するため、自動テストでは、エンドユーザーの使い方でアプリケーションが動作するかを検証するべきである」と述べている。

一方で、フロントエンドの自動テストライブラリTesting Libraryでは、テストがソフトウェアの使われ方に似てくるほど、より信頼性が高まるという考えに基づき、「アプリケーションの使われ方に近い形でテストが書けるような、メソッドやユーティリティのみを公開するようにしている。」と明言している(以下、原文)。

Guiding Principles - Testing Library
We try to only expose methods and utilities that encourage you to write tests that closely resemble how your web pages are used.

このように、自動E2Eテストでは、ユーザーの実際の使い方での動作検証を実行する必要がある。

自動E2Eテストにおけるテスタビリティの向上

アクセシビリティの向上

自動テストのテスタビリティについて調べていると、「アクセシビリティを向上すると結果として、テスタビリティは向上する。」という考えがみられた。

アクセシビリティが向上すると、機械、ブラウザや支援技術などのユーザエージェントにとってのコンテンツの読み取りやすさが向上する。ここで、機械にとってのコンテンツの読みやすさをマシンリーダビリティという。Webアプリケーションの自動テストも、機械がコンテンツを読み取り、操作することで、アプリケーションの動作を検査する。したがって、マシンリーダビリティの向上は、自動テストにおけるテスタビリティの向上に貢献する。すなわち、アクセシビリティの向上は、結果としてテスタビリティを向上させる。

もう少し具体的にアクセシビリティの向上についてみてみる。WebコンテンツやWebアプリケーションのアクセシビリティにおいて、重要な点は、意味的なHTML要素を使うことにある。これが意味することは、「できる限り、ふさわしい HTML 要素を、ふさわしい目的に使う」ということである。WebコンテンツやWebアプリケーションの開発には、ネイティブの HTML の機能を使用しつつ、必要に応じてWAI-ARIA[1]を使用して、アクセシビリティを向上することになる。

この「意味的なHTML要素を使うこと」は、「ユーザーの実際の使い方での動作検証を実行する」を助けてくれる。特に、WAI-ARIAで定義されたrole属性が役に立つ[2]。例えば、以下のようなコンテンツをテストをする場合を考える。

<form action="/login" method="POST" enctype="application/json">
  <div>
    <label for="username-field">username</label>
    <input type="text" id="username-field" required/>
  </div>
  <div>
    <label for="password-field">password</label>
    <input type="password" id="password-field" required/>
  </div>
  <button id="login">Login</button>
</form>

「ログインページのusername-fieldというid属性のついた要素にユーザ名を入力する」とした手順の場合、id属性は、ユーザにとっては、アプリケーションを使う上では、知る必要のない値のため、実際の使われ方からは離れてしまう。具体的なテストコードをPlaywrightで書いてみると以下のようになる。

test.describe("id属性で要素を指定してログイン", () => {
  test("正当なユーザー名とパスワードでログインできる", async ({ page }) => {
    await page.getById("username").fill("myUsername");
    await page.getById("password").fill("myPassword");
    await page.getById("login" }).click();

    // expected to success to login
  });

一方で、「ログインページのusernameというラベルのついた入力欄にユーザ名を入力する」とした手順の方が、より実際のユーザ操作に近い手順となる。つまり、role属性が付加されている(ここではrole=textboxが付加されている)ことで、自動テストを実際の使われ方に近づけることができる。具体的なテストコードをPlaywrightで書いてみる。

test.describe("role属性で要素を指定してログイン", () => {
  test("正当なユーザー名とパスワードでログインできる", async ({ page }) => {
    await page.getByRole("textarea",  { name: "username" }).fill("myUsername");
    await page.getByRole("textarea",  { name: "password" }).fill("myPassword");
    await page.getByRole("button", { name: "Login" }).click();

    // expected to success to login
  });

role属性を明示的に付加しなくても、意味的にふさわしいHTML要素を用いれば、ブラウザによりrole属性が補完される。

Testing Libraryでは、前述の「テストがソフトウェアの使われ方に似てくるほど、より信頼性が高まる」の考えに基づいて、ロケーティングに用いるクエリに優先度を設けている。この優先度に従えば、テストはソフトウェアの使われ方に近づく。また、プロダクションコードでは、この優先度で上位にあるクエリで要素を指定できるように実装することを心がけるとよい。
この優先度では、role属性を指定して要素を取得するAPIが最優先と定義されている。

data 属性

ロケータに、idclassnameといった属性これらを利用することは、アンチパターンと考えられる[3]。主な理由は、以下の通り。

  • id等のプロダクションコードの内部的な値を外部的なテストコードが参照している場合に、プロダクションコードの修正の容易性が損なわれる。
  • プロダクションコードを修正した場合の、テストコードのメンテナンスが都度発生する。変更に弱いテストコード。メンテナンスコストが高いテストコード。(一方で、テストコード自体を、プロダクションコードの変更に影響されづらいように工夫する必要は、別問題としてある。)

そこで、カスタムデータ属性(data-*)を使用することで、上記の問題を回避しつつ、以下のメリットが得られる。

  • テストで用いられるカスタム属性であることが明確なため、プロダクションコードとテストコードとの関係が明確になる。
  • 変更される可能性が低く、一意性のあるロケータとなる。

ちなみにTesting Libraryには、data-*属性を指定して要素を取得するAPIが提供されている。また、CypressBest Practiceとしても、data-*属性をセレクタとして使用することが推奨されている。
一方で、前述した「テストがソフトウェアの使われ方に似てくるほど、より信頼性が高まる」という考えから、Why You Should Avoid Testing React Components With Test IDsではdata-*属性をロケータに使用しない方がよいと述べている。ユーザーはその要素がどんな属性(ここではdata-*属性)を持っているかを知らなくても、操作できるので、この考え方に従わないためである。また、data-*属性はプロダクションコードに影響せずに、idの代替として有用ではあるが、結局管理コストがかかる点で問題がある。それでも、前述のメリットがあるので、ないよりはあった方がいいとは思う。

まとめ

  • Web フロントエンドの自動テストでは、ユーザーの実際の使い方での動作検証を実行するべきである。
  • Web フロントエンドの自動テストにおけるテスタビリティの向上にはプロダクションコードのアクセシビリティが重要になる。
  • data-*属性は、その使用目的を明確にでき、変更される可能性が低く、一意性のあるロケータとなるが、上記の方針に従わなくなる。しかし、id属性等を用いるよりはよいため、あった方がいい。

脚注
  1. MDNのARIAWAI-ARIAの基本は参考になる ↩︎

  2. その要素がどんな役割をもつか、もしくは何をするかを明確にする ↩︎

  3. idやclassを使ってテストを書くのは、もはやアンチパターンであるなぜE2Eテストでidを使うべきではないのかMaking your UI tests resilient to changeでも述べられている ↩︎

GitHubで編集を提案

Discussion