テストを科学する

2013/12/12

Seleniumでテスト失敗時のスクリーンショットを取得する方法

Selenium Advent Calendar 2013の第2弾ということで、今回は必ず誰もが欲しくなる「テストが失敗したときのスクリーンショットを取得する」方法についてご紹介します。

こんな方におすすめ

ようやくWebDriverに慣れてきて、テストケースも増えてきた。毎回自分で実行するのは面倒なので、ちゃんとCIサーバに載せて夜間自動実行しよう!となったときに、次の壁となって立ちはだかるのが「自動実行されたテストケースの解析」です。不具合なのか?テストケースに不備があるのか?それともネットワーク環境に問題があったのか?自分のPCで試してみても再現できなかったり再現箇所までに時間がかかってしまったりとつらいことが多いこの問題に直面している方におすすめの記事です。

※ちなみに、これまでの記事もJavaでサンプルコードを書いていましたが今回はより強くJavaのテストフレームワークに依存した内容になっております。予めご了承ください。

テストフレームワークを活用して共通処理の仕組みをつくろう

失敗時のスクリーンショットを取得する最も単純な仕組みとしては、
  「何か処理をしたらスクリーンショットを撮る」
というものが考えられます。たとえば、「スクリーンショットを撮ってボタンをクリックする」といった処理をまとめてメソッドにしておきクリックのときは必ずそれを使うといったコーディング規則ですね。金融系のシステムなどで、全画面のエビデンスが必要とされる場合にはこれでも十分です(作りによっては、肝心なエラーの画面でスクリーンショットを撮りそびれることもありますが…)。

ただ、毎回全エビデンスを取得していると保存先にも悩むことになりますし、NG時のエビデンスしか必要としない現場ではかえって邪魔になってしまうこともあるでしょう。では、「失敗時」に限ってスクリーンショットを撮るにはどうしたら良いのでしょうか?

具体的な方法は、Selenium自体というよりも使用しているテストフレームワークによって異なってきます。この記事では、TestNGJUnitの2つについてご紹介します。

TestNGの場合

Javaのテストフレームワークというと圧倒的に有名なのはJUnitかと思いますが、TestNGには色々と小回りの効く便利機能があり、特に筆者の試してみた限りですとSeleniumのテストをサクっと始めたい場合にはかなり相性が良いように見えます。JUnitはシンプルな機構の上に色々と拡張機能が付いている、TestNGは最初から豊富な機能が付いているという印象ですね。

JUnitには各テストメソッド後に実行される@Afterというアノテーションがありますが、TestNGにも似たような@AfterMethodというアノテーションがあります。両者の違いは、@AfterMethodはテスト結果を引数として取れるということです。これを使って、テストのステータスによって処理を切り分けることができます。


@AfterMethod(alwaysRun = true)
public void afterMethod(ITestResult result) {
  // 結果が「成功」でなければスクリーンショットを撮る
  if (result.getStatus() != ITestResult.SUCCESS)
    {
      takeScreenShot(result.getName());

      // その他ログ出力など
    }
  }
}

takeScreenShot()は、引数をファイル名に使ってスクリーンショットを撮ってくれる便利関数ということにします。今回のメインの処理を分かりやすくするためここでは詳細は記載しませんが、興味のある方はAdvent Calendarの他の記事を見てみてください。result.getName()ではテストメソッドの名前を取ることができますので、これをファイル名に入れておくと後で解析がしやすくなります。

このafterMethod()を実装した基底テストクラスを1つ作っておき、個別のテストクラスはこの基底クラスを継承するようにすればいちいちスクリーンショット取得のコードを沢山書く必要はなくなります。

JUnitの場合

JUnitの場合は、少しだけ書き方が複雑になります。JUnitには4.7から導入されたRuleという仕組みがあり、これまでコントロールできなかったテスト実行前後の細かい処理を制御できるようになっています。Rule自体について解説を始めると非常に長くなってしまいますので、ここでは今回の目的に沿ったものだけご紹介します。

テストの実行を監視するTestWatcherルール

デフォルトで準備されている「TestWatcher」ルールではテスト開始時・終了時・成功時・失敗時の動作がそれぞれメソッドとして用意されており、これを実装することで好きに動作をコントロールできます。Seleniumのテストの場合であれば、たとえば
 ・開始時:WebDriverを初期化する
 ・終了時:WebDriverを終了する
 ・成功時:特に何もしない
 ・失敗時:スクリーンショットを撮る
といった動作が考えられます。

実装してみると、以下のようになります。


public class SeleniumTestWatcher extends TestWatcher {

  private WebDriver driver;

  @Override
  protected void starting(Description description) {
    driver = new FirefoxDriver();
  }

  @Override
  protected void finished(Description description) {
    driver.quit();
  }

  // 失敗したときはスクリーンショットを撮る
  @Override
  protected void failed(Throwable e, Description description) {
    super.failed(e, description);
    takeScreenShot(String.format("%s_%s",
      description.getClassName(), description.getMethodName()));
  }

}

テストケース側からの呼び出し

次に、作ったRuleをテストケース側から呼び出す場合の書き方です。呼び出す場合は必ず「@Rule」アノテーションを付け、「publicスコープで」定義します。このようにフィールドとして定義するだけで、「このケースではこのRuleを使う」という取り決めになります。また、そのままではRuleとテストケースでWebDriverオブジェクトを共有できないため、setUp()内でインスタンスの受け渡しをしています。TestNGの場合と同様、こちらも全テストケースの基底クラスとして1つ作ってしまうと良いでしょう。

public class SampleTestCase {
  private WebDriver driver;
    
  // publicにすることが必要
  @Rule
  public SeleniumTestWatcher watcher = new SeleniumTestWatcher();
    
  @Before
  public void setUp() {
    // TestWatcherからWebDriverオブジェクトを受け取る
    driver = watcher.getWebDriver();
  }

まとめ

今回はSeleniumのテストを自動実行した際に起きる解析の難しさを軽減するためのテクニックをご紹介しました。自動テストは作るだけでなく運用してこそ意味がありますので、「自動テストを始めたらかえって毎日の解析が辛くなってしまった…」ということのないように、こういったちょっとした改善を積み重ねていくことは本当に大切です。Advent Calendarはまだまだ続いておりますので、こういったナレッジをお持ちの皆様はぜひとも共有して頂ければと思います。

SHIFTでは、CI(Jenkins)導入だけでなくSelenium等のツールを使った自動テストスクリプト作成&ご支援も承っております。自動テストを試してみたいけど、最初の調査がちょっとしんどいな…と感じている方は、お気軽にご相談下さい。


この記事は、Selenium Advent Calendar 2013の12日目の記事です。明日はkuronekomichaelさんです!

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

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