テストを科学する

1月18日に開催された「第1回 日本Seleniumユーザーコミュニティ勉強会」に参加させて頂きました。

かなり以前からWebアプリケーション向けの自動テストツールとして人気を博しているSeleniumですが、実は日本でユーザーコミュニティができたのは昨年(2013年)の7月とごく最近です。コミュニティと今回の勉強会を主催されている株式会社TRIDENTの伊藤社長によると、ユーザー数は半年で約200名弱まで増えているそうです。

記念すべき第1回となった今回の勉強会ではSeleniumの生みの親であるJason Huggins氏が来日され、
・ プロジェクトの本体であるSelenium
・ 今日では欠かせない要素となったモバイルアプリをテストするためのAppium
・ ハードで自動テストを実現するロボット tapster
の三本立てで熱い講演をしてくださいました。質疑では昨年末のリリースが見送りとなったSelenium3などに関して質問が次々に投げかけられ、あっという間に40分以上が経過するという盛り上がりぶりでした。

弊社玉川からもJenkinsとSeleniumの連携の基本ということでLTをさせていただきましたので、こちらにスライドを掲載いたします。

12月20日に開催された「第8回Jenkins勉強会」で、Jenkinsの有償版であるJenkins Enterprise By CloudBeesについてお話をしてきました。

デモが無いので少し情報が薄くなってしまっておりますが、こちらにスライドを公開しています。

Jenkins Enterprise By CloudBeesは、OSS版Jenkinsの開発にも大きく貢献している(というよりも、ほとんどのコードを書いている)CloudBees社の強力なエンジニア達が開発した大規模開発向けのプラグインとテクニカルサポートのサービスから成り立っています。発表ではプラグインの顔ぶれの紹介とTemplates PluginやRole-Based Access Control Pluginなどいくつかの機能のデモをさせて頂き、これまで有償版を知らなかったという方からも沢山のご意見を頂けました。

会場の反応や各資料のURL等はTogetterからご覧頂けます。


年内のブログ更新はこのエントリーで最後になります。来年も、Jenkinsや自動テストに関わる様々な情報を発信していきたいと思います。

良いお年を!

この記事はPowerShell Advent Calendar 2013の24日目のエントリーです。

クリスマス・イブですが普通に仕事をして普通に家で大好きなシェルとSQLと戯れる@oota_kenです。

去年のPowerShell Advent Calendar 2012の17日目のエントリーで解説されているPowerShell用のBDDフレークワークPesterのMock機能に関してもう少し詳しく解説したいと思います。

下記のような時間帯毎に挨拶メッセージを出してくれる関数Out-Greetingのユニットテストを書く場合どのようにすればよいでしょうか?
# Out-Greetingはシェルの起動時に$profileで読み込んで画面表示をするといった方法で使用するイメージです。

function Out-Greeting([string] $name) {  
    $date = Get-Date
    $hour = $date.Hour
    if (6 -le $hour -and $hour -le 11) {
        $greeting = "Good morning "
    } elseif (12 -le $hour -and $hour -le 17) {
        $greeting = "Good afternoon "
    } else {
        $greeting = "Good night "
    }
    
    $greeting + $name | Out-Host
}

Get-Dateで現在日時を取得し、Out-Hostでコンソール出力してしまっているので、テスト結果が実行する日時に依存してしまって、そのままでは単に呼び出しができたかどうかの検証しかできませんね。

そのような場合にPesterのMock機能が活用できます。

Describe "Out-Greeting" {
    Context "Morning" {
        Mock Get-Date { return [DateTime] "2013/12/24 06:00:00" }
        Mock Out-Host -verifiable -parameterFilter { $_ -eq "Good morning ootaken" }
        It "at 6" {
            Out-Greeting "ootaken" 
            Assert-VerifiableMocks
        }
    }
}

PesterのMockは任意のコマンドレットや関数を上書きすることができます。また、同時に”-verifiable”オプションとAssert-VrifiableMocksを組み合わせてモックの呼び出しの検証を、”-parameterFilter”オプションによって呼び出し時の引数に基づくモックの呼び分けができます。

Get-Dateをモックで置き換えることにより、検証したい任意の時間で、実行結果の期待値を一意に定めて再現性あるテストを記述できるようになります。

また、Out-Hostをモックに置き換え、”-parameterfilter”オプションで引数をチェックすることにより、コンソールに出力するOut-Hostのような通常では検証が難しいコマンドレットを使った場合でも呼びだしと出力の検証ができるようになります。

このようにPesterのモック機能を使えば、テスト対象が現在日時や乱数、コンソールに依存したものであってもそれらをモックに置換し検証することが簡単にできます。

下記のように、モックのスコープはスクリプトブロック毎であり、並列してコンテキストでは別々のモックを用意し、各日時とそれに対応した挨拶の文言を検証することができます。

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
    $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
    . "$here¥$sut"

    Describe "Out-Greeting" {
        Context "Morning" {
            Mock Get-Date { return [DateTime] "2013/12/24 06:00:00" }
            Mock Out-Host -verifiable -parameterFilter { $_ -eq "Good morning ootaken" }
            It "at 6" {
                Out-Greeting "ootaken" 
                Assert-VerifiableMocks
            }
        }

        Context "Afternoon" {
            Mock Get-Date { return [DateTime] "2013/12/24 12:00:00" }
            Mock Out-Host -verifiable -parameterFilter { $_ -eq "Good afternoon ootaken" }
            It "at 12" {
                Out-Greeting "ootaken" 
                Assert-VerifiableMocks
            }
        }

        Context "Night" {
            Mock Get-Date { return [DateTime] "2013/12/24 18:00:00" }
            Mock Out-Host -verifiable -parameterFilter { $_ -eq "Good night ootaken" }
            It "at 18" {
                Out-Greeting "ootaken" 
                Assert-VerifiableMocks
            }
        }
    }

このように便利なPesterのモック機能ですが、そもそもモックを多用しなければテスト出来ない関数やスクリプトブロックというのはテスト容易性が低く、外部依存が強い、良くないコードです。

Out-Greetingの時刻によって挨拶を返す部分を切り出した下記のような関数Get-Greetingを作れば、モックの必要が無く、少ないコードで分岐のロジックを検証することができます。

function Get-Greeting([datetime] $date) {
    $hour = $date.Hour
    if (6 -le $hour -and $hour -le 11) {
        return "Good morning"
    } elseif (12 -le $hour -and $hour -le 17) {
        return "Good afternoon"
    } else {
        return "Good night"
    }
}

Describe "Out-Greeting" {
    Context "Get-Greeting" {
        It "at 6" {
           $result = Get-Greeting ([DateTime]  "2013/12/24 06:00:00")
           $result | Should Be "Good morning"
       }

       It "at 12" {
           $result = Get-Greeting ([DateTime]  "2013/12/24 12:00:00")
           $result | Should Be "Good afternoon"
       }

       It "at 18" {
           $result = Get-Greeting ([DateTime]  "2013/12/24 18:00:00")
           $result | Should Be "Good night"
       }
   }
}

以上のようにPesterのモック機能を使えば、テスト対象が現在日時や乱数、コンソールに依存したものであってもそれらをモックに置換し検証することが簡単にできます。

しかし、モックを多用したテストコードを書く前にそもそも対象の関数やスクリプトブロックは果たして適切なのだろうか、よりテストしやすい形にリファクタリングする余地は残されていないだろうか?と自答するのが真のエンジニアであり、PowerShellerです。ツールが豊富な機能を用意してくれているからといってそれにおぼれず常に最適な方法を自分の頭で考えましょう。

次の最終日はAkira SugiuraさんでPowerShell勉強会のフォローアップとのことです。楽しみですね。

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さんです!

2013/12/05(木)に開催された第4回GREE Tech Talk『スマートフォン時代のソフトウェアテスト』にお招きいただき、パネルディスカッション『Jenkinsによるテスト自動化の会社への導入』でお話しさせていただきました。

会社で自動化を導入する際に鬼門となる経営陣への説明とチームの成熟度に合わせた自動化の進め方を中心として、会場の皆さんからの質問を交えたアットホームな雰囲気でパネルをさせていただきました。

Jenkinsに特化した技術バリバリのパネルではなく、アジャイル開発の導入のような柔らかなお話になりましたが、Jenkins自体は非常に分かりやすく使いやすいツールである故に自動化の導入はこのような人や組織のソフト面でのがんばりが利いてきます。

SHIFTの自動化ご支援のメニューではこのような組織やチームにあわせた導入をご用意しておりますので、自動化の導入で課題があるなどございましたら、是非ご相談下さい。

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

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