テストを科学する

2013/11/05

PageObjectデザインパターンを利用して画面変更に強いUIテストを作成する

今回は、Selenium2(WebDriver)でテストケースを作成するときに知っていると便利な「PageObjectデザインパターン」をご紹介します。

こんな方におすすめ

SeleniumはWebブラウザ上で動作するアプリケーションの自動テストツールの中では圧倒的な知名度のあるツールです。2011年にSelenium2がリリースされてからは大きくアーキテクチャが変わり使い勝手も向上しましたが、実際に大量のテストケースを作成・保守するとなると書き方に工夫をする必要があります。

PageObjectデザインパターンの考え方は、
・WebDriverでテストを自動化しているけど、画面変更にテストケースの修正が追いつかず困っている
・WebDriverでテストを自動化したいけど、どのようにテストケースを作成すれば良いのかわからない
という方々にとってとても参考になります。

PageObjectとは?

PageObjectデザインパターンとは、アプリケーションの画面を1つのオブジェクトとしてとらえるデザインパターンの1種のことです。Seleniumの公式サイトでも推奨されている、保守性の高いテストコードの書き方です。

公式サイトに記載されているPageObjectの原則は以下のようなものです。

・The public methods represent the services that the page offers(publicメソッドは、ページが提供するサービスを表す)
・Try not to expose the the internals of the page (ページの内部を公開しないこと)
・Generally don’t make assertions (原則としてassertionを行わないこと)
・Methods return other PageObjects (メソッドは他のPageObjectsを返す)
・Need not represent an entire page (ページ全体を表す必要はない)
・Different results for the same action are modelled as different as different methods (同じアクションに対して異なる結果となる場合には異なるメソッドとしてモデル化する)

この考え方の利点は、テスト対象となるアプリケーションのレイアウトが変更されてもテストコードの変更は最小限で済むということです。

実際のコードを見てみましょう

PageObjectデザインパターンでテストコードを記述するには、コードを大まかに2種類に分割します。

・画面を操作するためのPageクラス
  ・画面やダイアログの単位で作成
  ・ボタンやテキストフィールドなどのHTML要素を保持
  ・各要素を操作するためのAPIを定義
・Pageクラスを操作してテストシナリオを記述するクラス
  ・原則としてPageクラスのAPIだけを呼ぶ
  ・直接HTML要素を操作しない

以下はPageObjectを利用したテストコードの書き方の例となります。
ECサイトで商品の購入を行うテストを書く場合を考えてみましょう。

◆Pageクラス

public class 商品検索ページ {
  WebDriver driver;
  // ページ内の要素を定数として定義
  By textKeyword = By.name("keyword");
  By buttonSearch = By.id("search");

  public 商品検索ページ(WebDriver driver) {
    this.driver = driver;
  }

  // そのページで行える動作をメソッドとして定義
  public 商品検索ページ 商品検索を行う(String keyword) {
    driver.findElement(textKeyword).sendKeys(keyword);
    driver.findElement(buttonSearch).click();
    
    return this;
  }

  public 商品詳細ページ 商品詳細ページに遷移する(String name) {
    // (詳細ページに遷移する処理)
    return new 商品詳細ページ(driver);
  }
}

public class 商品詳細ページ {
  WebDriver driver;

  public 商品詳細ページ(WebDriver driver) {
    this.driver = driver;
  }

  public 商品詳細ページ 商品をカートに入れる() {
    // (カートに入れる処理)
    return this;
  }

  public 購入確認ページ 購入確認画面に遷移する() {
    // (画面遷移の処理)
    return new 購入確認ページ(driver);
  }
}

◆テストシナリオ

@Before
public void シナリオ実行前に必要な処理() {
  (new ログイン画面(driver))
  .ログインする()
  .商品画面に遷移する();
}

@Test
public void 購入フローのテスト() {
  (new 商品画面(driver))
  .商品検索を行う("商品名")
  .商品詳細画面に遷移する()
  .商品をカートに入れる()
  .購入確認画面に遷移する()
  .お届け先を選択する(自宅)
  .支払い方法を選択する(代引)
  .注文確認画面に遷移する()
  .注文終了後にホーム画面に遷移する();
}

PageObjectデザインパターンのメリット

この例のようにテストコードをページとシナリオの二層に分けることで、2つのメリットが得られます。

1つは、メソッドチェーンを使うことでシナリオの可読性が上がる点です。
余計なコードがなく、必要な動作が続けて記載されているためどのようなテストをしているのかを把握しやすくなります。
サンプルのPageクラスの方の実装を見ると分かりますが、
 ・画面遷移が発生する場合は遷移先の画面のオブジェクトを返却
 ・画面遷移が発生しない場合は自分自身の画面のオブジェクト(this)を返却
することでスムーズにメソッドをつなぐことができます。

もう1つのメリットは、アプリケーションのレイアウト変更に強いことです。
たとえば商品検索ページの検索ボタンの位置が変わった場合、修正を行うのは「商品検索ページ」クラスの要素の定義部分だけで良く、テストシナリオには変更を加える必要がなくなるため、変更箇所を最低限に抑えることができます。
また、各画面をクラスとして定義しているため、レイアウト変更があった際にどの部分を修正すれば良いのかを容易に判断することができます。


今回は、PageObjectという考え方を使ってWebDriverのテストコードの可読性・保守性を向上させる方法をご紹介しました。今後もより便利なWebDriverの使い方などご紹介していきます。

ソフトウェアテストに関するお悩みなど、まずはお気軽にお問い合わせください。

  • お問い合わせフォーム【お問い合わせはコチラ】
  • 電話でのお問い合わせ【0120-142-117】