
こんにちは!みなさん、テストしてますか?
第2回の前編では、E2Eテストの基幹部分とも言える要素探索の技術の変遷について扱いました。中編では、前編の内容も踏まえつつ、様々な実装技術について説明していきたいと思います。
記事一覧:モダンなE2Eテストの考え方をマスターしよう
実装技術の変遷
ここで言う実装技術とは、例えば第1回でテストコードを作成するのに使ったPlaywright Codegenのようなものを指します。単にテストコードを書くだけでなく、機械的にテストコードを生成する技術だったり、テストコードの可読性を上げるための書き方を考えたりなど、様々な試行錯誤が現在進行系で行われています。この記事では、筆者が知るいくつかの代表的な手法を取り上げたいと思います。
レコードアンドリプレイ
レコードアンドリプレイは、テスト対象のアプリケーションへの操作を「記録し、再生する」手法です。キャプチャアンドリプレイ、レコードアンドプレイバックなどとも呼びます。普段GUIを操作するのと全く同じ感覚で自動テストを実装できるので、非常にとっつきやすく、コーディングの知識がまるで無い人にとっても扱える他、ツールそのものへの学習も最小限に出来るのが特徴です。
原理としては、テスト対象のアプリケーションに記録用のコードを埋め込み、クリックや文字入力、スクロールなどのイベントを逐一記録していく、といったものです。
上記のように、誰でも簡単に使える反面、細かく記録しすぎてしまうことによるテストの不安定さや、生成されるテストコードの質が悪くメンテナンス性が著しく悪い、などの課題がありました。
特に、生成されるコードの質のところでは、当時使われていたページ表現の記法がCSSセレクターやXPathなどの内部構造であったこともあり、記録されたコードを後で読み返すと何をやっているのか良くわからない、ということもしばしばでした。
レコードアンドリプレイを採用するツールは多数ありますが、有名なのはSelenium IDEでしょう。その他Ghost Inspectorなども存在します。
レコードアンドリプレイはなぜダメだったのか
先述の通り、レコードアンドリプレイツールはたびたび批判の憂き目に合っています。書籍『システムテスト自動化標準ガイド』※1では、一章まるごと使ってこの手法への批判を記載しており、当時いかにこの手法によって苦しむ人が多かったのかが垣間見えます。
※1『システムテスト自動化 標準ガイド』(Mark Fewster、Dorothy Graham 著/テスト自動化研究会 訳/翔泳社)
レコードアンドリプレイの欠点として良く挙げられるのは以下のような点です。
- 記録されるロケーターの可読性が低かったり、ちょっとしたページの構造の変化に弱かったりする
- スクロールなど不要なステップの記録が多く、実行の不安定さにつながる
- テストコードが構造化されておらず、再利用性が低い
ですが、みなさんは第1回ですでにレコードアンドリプレイを経験して、(簡単な例ではあるものの)素早くテスト自動化ができることを実感していますね。また、(1) については第2回の前編でセマンティクスベースの要素探索により、可読性が高くページの構造の変化に強いロケーターを作れることを知っています。
(2) についても、第1回で使ったPlaywright Codegenではスクロールなどのステップは記録されておらず、余計なステップの記録はさほど発生しないことが分かったかと思います。
(3) については、それぞれのツールごとに考え方が異なりますが、後に記載するPage Object Model(POM)形式での構造化が一般的に使われており、サポートしているツールも多いです。
というわけで、過去にレコードアンドリプレイが使い物にならなかったのはコンセプトが悪かったからではなく、技術的に追いついていない部分が多かっただけというのが筆者の結論です。実際、Playwrightのようなツールにもビルトインされていますし、最近流行りのAIツールでもレコードアンドリプレイ形式を採用しているものは多いです(AIが操作した内容をレコードアンドリプレイで記録する、なんてツールもあります)。
キーワード駆動テスト
さて、レコードアンドリプレイでは操作を記録することでテストを作りましたが、キーワード駆動テストではキーワードと操作対象、データを組み合わせてテストを書きます。例えば、次のようなイメージです。
| キーワード | ロケーター | データ |
|---|---|---|
| ページを開く | – | http://example.com |
| 文字を入力する | textbox#email | john.example.com |
| 文字を入力する | textbox#password | password |
| クリックする | span#button | – |
少しでもプログラミングをかじったことがある方なら、なんとなくこの記法がプログラミングと自然言語の合いの子のように見えるかもしれません。
このように、ある特定の分野(ドメイン)の課題を解決するために使われる、人間にとって読みやすく機械にとっても処理しやすい言語をDSL(Domain Specific Language)と呼びます。つまり、キーワード駆動テストはE2Eテスト用のDSLです。
読みやすい一方、出来ることがDSLとして定義されたもののみに絞られてしまうことが難点です。また、場合によっては操作対象のロケーターを読みやすくするためにたくさんのエイリアスを設定しなければならず、テストのための実装が必要以上に増えてしまい、テストを書き始めるまでの労力が比較的大きいです。
キーワード駆動テストを採用するツールの代表例として、Robot Frameworkというものがあります。また、JavaScriptの文法の中でキーワード駆動のDSLのようなものを実現した珍しいツールでCodeceptJSというものもありますので一緒に紹介しておきます。
ページオブジェクトモデル
ページオブジェクトモデル(以下POM)は、一つのページに関連する要素と操作をひとまとめにしたものです。例えば、ログインページのページオブジェクトモデルは次のようになります。
// loginPage.js
class LoginPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
this.page = page;
this.emailInput = page.locator('#email');
this.passwordInput = page.locator('#password');
this.loginButton = page.locator('#login-form button.submit');
}
async goto() {
await this.page.goto('https://example.com/login');
}
async fillEmail(email) {
await this.emailInput.fill(email);
}
async fillPassword(password) {
await this.passwordInput.fill(password);
}
async submit() {
await this.loginButton.click();
}
async login(email, password) {
await this.fillEmail(email);
await this.fillPassword(password);
await this.submit();
}
}
module.exports = { LoginPage };
すると、テストコード側からはLoginPageという名前で、ページ内で使われる様々な要素と、ログインなどページ内で利用するアクションを定義できます。
// example.spec.js
const { test, expect } = require('@playwright/test');
const { LoginPage } = require('./loginPage');
test('ユーザーがログインできる', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('john@example.com', 'securePassword!');
// たとえばログイン成功後に表示されるテキストを確認
await expect(page.locator('text=ようこそ')).toBeVisible();
});
利点としては、キーワード駆動テストと同様にページ内のロケーター記述を隠蔽できる点と、「ログイン」などの一連の操作をひとまとめにしたショートハンドを作成してテストコードの記述を省力化できる点にあります。また、特定のページに対する操作であることが一目で分かるため、コードの可読性向上にもつながります。加えて、ページの構成が変わった場合などにも、POMだけを直せば良いので、メンテナンス性が向上します。
反面、キーワード駆動テストと同様、POMそのもののメンテナンスが煩雑になりがちです。POMはページ内の要素とアクションを抽象化したものです。言い換えると、POMを定義することは、アプリケーションのUIそのものの実装と、そのページの見た目と操作方法を定義するテスト用の実装の2つを作る必要があります。これは典型的なダブルメンテで、面倒に思う場合も多いでしょう。
BDD(振る舞い駆動テスト)
BDD(振る舞い駆動テスト)は、その名の通りアプリケーションの「振る舞い」に着目した実装方法です。代表的なツールはCucumberです。
BDDでは、アプリケーションの振る舞いを自然言語に近い形で記述したFeatureと、実際にテスト対象を自動操作するためのStep Definitionファイルに分けて記述します。
Feature
Feature: ユーザーログイン機能
ユーザーとして
システムにログインしたい
なぜなら、個人の情報やサービスにアクセスするため
Background:
Given ログインページが表示されている
Scenario: 正しい認証情報でのログイン成功
When 有効なメールアドレス "user@example.com" を入力する
And 正しいパスワード "password123" を入力する
And "ログイン" ボタンをクリックする
Then ダッシュボードページにリダイレクトされる
And "ようこそ、user@example.com さん" というメッセージが表示される
Step Definition
Given('ログインページが表示されている', async function() {
await driver.get('http://localhost:3000/login');
// ページタイトルを確認してログインページであることを検証
const title = await driver.getTitle();
expect(title).to.include('ログイン');
});
When('有効なメールアドレス {string} を入力する', async function(email) {
const emailField = await driver.findElement(By.id('email'));
await emailField.sendKeys(email);
});
When('正しいパスワード {string} を入力する', async function(password) {
const passwordField = await driver.findElement(By.id('password'));
await passwordField.sendKeys(password);
});
When('ログインボタンをクリックする', async function(buttonText) {
await driver.findElement(By.id('login-button'));
await loginButton.click();
});
// 検証系ステップ - 成功パターン
Then('ダッシュボードページにリダイレクトされる', async function() {
const currentUrl = await driver.getCurrentUrl();
expect(currentUrl).to.include('/dashboard');
});
Then('{string} というメッセージが表示される', async function(expectedMessage) {
const actualMessage = await welcomeMessage.getText();
expect(actualMessage).to.equal(expectedMessage);
});
技術的には、BDDはアプリケーションの振る舞いを表したFeatureドキュメントと、テスト対象を自動操作するStep Definitionコードとを組み合わせたものです。ですが、実際にはBDDを単なる技術として扱ってしまうと冗長な部分が多く、開発のオーバーヘッドになりかねません。
BDDはキーワード駆動テストのようにDSLでテストコードを記述する手法ではなく、要求仕様から開発をスタートし、要求仕様を表現するテストを中心に開発を駆動するためのプラクティスです。テストコードとは別にFeatureを独立した自然言語のドキュメントとして切り出すのは、開発者だけでなく様々なステークホルダーと一緒に要求仕様を詰め、誤解を最小限にしたうえで開発を進めるためです。
また、BDDは必ずしもE2Eテストだけのものではなく、単体テストなどでもよく用いられます。ただし、コードレベル(単体テスト)の振る舞いとユーザーレベル(E2Eテスト)の振る舞いとでは異なる部分が多いため、ユーザーレベルのBDDを特にATDD(受け入れテスト駆動開発)と呼ぶ場合もあります。
実装手法の移り変わりと再評価
ここに挙げた自動テスト実装手法は、必ずしもどれが正しいとか、どれが最新だとかというものではありませんが、他の周辺技術の影響などを受けて多少の流行り廃りがあります。
例えば、最初に挙げた「レコードアンドリプレイ」という実装方法は、節の中でも記載していた通り、ある時代には非常に使うユーザーが多かったのですが、その後メンテナンス性の低さなどから批判を受け、キーワード駆動型の記述に乗り換えました。
一方で、ここ数年よく使われている、AIを利用した自動テストツールの多くはレコードアンドリプレイを採用しているケースが多いです。これは、レコードアンドリプレイは直感的で使いやすいという理由だけでなく、記録した要素やユーザー操作から様々な情報を得て自動テストに活かせるという、コンピューターによるコード生成ならではの利点があります。
そのため、「書籍で紹介されていたから」「ベストプラクティスらしいから」という理由で安易に実装方法を選んでしまうのでは危険です。チームの成熟度や手法の特性などを考慮しながら技術選定を進めると良いでしょう。一番のおすすめは、とにかく全部一度触ってみてから、一番しっくりくるものを使ってみることです。
まとめ
この中編では、様々な実装技術について扱いました。後編では、より根幹となる自動操作の技術と、E2Eテストの目的の変遷について解説したいと思います。

