PHPUnitによるテスト:応用編

2010年4月28日(水)
伊丹 久兼

Selenium RCを使ったブラウザテストの自動化(1)

 これまで紹介してきたテストは、アプリケーションの構成要素であるモジュールを対象とした単体テストでした。一 方、PHPで開発するアプリケーションと言えば、もっぱらWebシステムとなりますので、結合テストではWebブラウザを介した画面操作の確認を行いま す。一口にブラウザと言っても、Internet Exploroer(IE)、Firefox、Safari、Chromeなど複数の種類があり、またそれぞれバージョンごとに表示や動作に違いがありま す。

 PHPUnitでは、この手間のかかるテストを自動化する仕組みとして、「PHPUnit_Extensions_SeleniumTestCase」 という名前の拡張テスト・クラスを用意しています。「Selenium RC」(http://seleniumhq.org/projects/remote-control/) というWebブラウザをプログラムから制御するテスト・ツールと連携させることで、「任意のURLを開き、フォームに値を入力し、サブミットした後の画面 表示を確認する」といった一連の操作をPHPUnitのテスト・ケースとして実行できます。

 まず、Selenium RCをインストールして、起動しておきます。
--------------------------------------------------------------------------------
# mkdir /opt/selenium-remote-control-1.0.3
# cd /opt/selenium-remote-control-1.0.3
# wget http://selenium.googlecode.com/files/selenium-remote-control-1.0.3.zip
# unzip selenium-remote-control-1.0.3.zip
# java -jar selenium-server-1.0.3/selenium-server.jar
--------------------------------------------------------------------------------

 テスト・ケースは、次のようになります。setUp()の中で、ブラウザの種類と、テスト対象サイトのベースURLを設定します。実際に動作させる場合 には、接続先となるWebサイトの迷惑とならないよう、ローカルの実験用サイトを指定しましょう。

 testTitle()でのテスト内容は次の通りです。

(1)指定されたURLをWebブラウザで開く
(2)ページがロードされるまで5000秒待つ
(3)

タグの内容が"Selenium Test Page"と等しいか確認<br /> --------------------------------------------------------------------------------<br /> <?php <br /?> require_once 'PHPUnit/Extensions/SeleniumTestCase.php';<br /> <br /> class SeleniumTest extends PHPUnit_Extensions_SeleniumTestCase<br /> {<br />   protected function setUp()<br />   {<br />     <span class="MathJax_Preview" style="color: inherit;"><span class="MJXp-math" id="MJXp-Span-1"><span class="MJXp-mi MJXp-italic" id="MJXp-Span-2">t</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-3">h</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-4">i</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-5">s</span><span class="MJXp-mo" id="MJXp-Span-6" style="margin-left: 0.267em; margin-right: 0.267em;">−</span><span class="MJXp-mo" id="MJXp-Span-7" style="margin-left: 0.333em; margin-right: 0.333em;">></span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-8">s</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-9">e</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-10">t</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-11">B</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-12">r</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-13">o</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-14">w</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-15">s</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-16">e</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-17">r</span><span class="MJXp-msup" id="MJXp-Span-18"><span class="MJXp-mo" id="MJXp-Span-19" style="margin-left: 0em; margin-right: 0.05em;">(</span><span class="MJXp-mo MJXp-script" id="MJXp-Span-20" style="vertical-align: 0.5em;">′</span></span><span class="MJXp-mo" id="MJXp-Span-21" style="margin-left: 0.267em; margin-right: 0.267em;">∗</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-22">f</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-23">i</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-24">r</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-25">e</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-26">f</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-27">o</span><span class="MJXp-msup" id="MJXp-Span-28"><span class="MJXp-mi MJXp-italic" id="MJXp-Span-29" style="margin-right: 0.05em;">x</span><span class="MJXp-mo MJXp-script" id="MJXp-Span-30" style="vertical-align: 0.5em;">′</span></span><span class="MJXp-mo" id="MJXp-Span-31" style="margin-left: 0em; margin-right: 0em;">)</span><span class="MJXp-mo" id="MJXp-Span-32" style="margin-left: 0em; margin-right: 0.222em;">;</span><span class="MJXp-mo" id="MJXp-Span-33" style="margin-left: 0.333em; margin-right: 0.333em;"><</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-34">b</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-35">r</span><span class="MJXp-mrow" id="MJXp-Span-36"><span class="MJXp-mo" id="MJXp-Span-37" style="margin-left: 0.111em; margin-right: 0.111em;">/</span></span><span class="MJXp-mo" id="MJXp-Span-38" style="margin-left: 0.333em; margin-right: 0.333em;">></span></span></span><script type="math/tex" id="MathJax-Element-1">this->setBrowser('*firefox');<br />     </script>this->setBrowserUrl('<a href="http://example.com%27/">http://example.com'</a>);<br />   }<br /> <br />   public function testTitle()<br />   {<br />     <span class="MathJax_Preview" style="color: inherit;"><span class="MJXp-math" id="MJXp-Span-39"><span class="MJXp-mi MJXp-italic" id="MJXp-Span-40">t</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-41">h</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-42">i</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-43">s</span><span class="MJXp-mo" id="MJXp-Span-44" style="margin-left: 0.267em; margin-right: 0.267em;">−</span><span class="MJXp-mo" id="MJXp-Span-45" style="margin-left: 0.333em; margin-right: 0.333em;">></span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-46">o</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-47">p</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-48">e</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-49">n</span><span class="MJXp-msup" id="MJXp-Span-50"><span class="MJXp-mo" id="MJXp-Span-51" style="margin-left: 0em; margin-right: 0.05em;">(</span><span class="MJXp-mo MJXp-script" id="MJXp-Span-52" style="vertical-align: 0.5em;">′</span></span><span class="MJXp-msup" id="MJXp-Span-53"><span class="MJXp-mrow" id="MJXp-Span-54" style="margin-right: 0.05em;"><span class="MJXp-mo" id="MJXp-Span-55" style="margin-left: 0.111em; margin-right: 0.111em;">/</span></span><span class="MJXp-mo MJXp-script" id="MJXp-Span-56" style="vertical-align: 0.5em;">′</span></span><span class="MJXp-mo" id="MJXp-Span-57" style="margin-left: 0em; margin-right: 0em;">)</span><span class="MJXp-mo" id="MJXp-Span-58" style="margin-left: 0em; margin-right: 0.222em;">;</span><span class="MJXp-mo" id="MJXp-Span-59" style="margin-left: 0.333em; margin-right: 0.333em;"><</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-60">b</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-61">r</span><span class="MJXp-mrow" id="MJXp-Span-62"><span class="MJXp-mo" id="MJXp-Span-63" style="margin-left: 0.111em; margin-right: 0.111em;">/</span></span><span class="MJXp-mo" id="MJXp-Span-64" style="margin-left: 0.333em; margin-right: 0.333em;">></span></span></span><script type="math/tex" id="MathJax-Element-2">this->open('/');<br />     </script>this->waitForPageToLoad("5000");<br />     <span class="MathJax_Preview" style="color: inherit;"><span class="MJXp-math" id="MJXp-Span-65"><span class="MJXp-mi MJXp-italic" id="MJXp-Span-66">t</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-67">h</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-68">i</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-69">s</span><span class="MJXp-mo" id="MJXp-Span-70" style="margin-left: 0.267em; margin-right: 0.267em;">−</span><span class="MJXp-mo" id="MJXp-Span-71" style="margin-left: 0.333em; margin-right: 0.333em;">></span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-72">a</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-73">s</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-74">s</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-75">e</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-76">r</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-77">t</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-78">E</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-79">q</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-80">u</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-81">a</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-82">l</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-83">s</span><span class="MJXp-mo" id="MJXp-Span-84" style="margin-left: 0em; margin-right: 0em;">(</span><span class="MJXp-mo" id="MJXp-Span-85" style="margin-left: 0.333em; margin-right: 0.333em;">"</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-86">S</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-87">e</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-88">l</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-89">e</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-90">n</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-91">i</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-92">u</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-93">m</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-94">T</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-95">e</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-96">s</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-97">t</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-98">P</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-99">a</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-100">g</span><span class="MJXp-mi MJXp-italic" id="MJXp-Span-101">e</span><span class="MJXp-mo" id="MJXp-Span-102" style="margin-left: 0.333em; margin-right: 0.333em;">"</span><span class="MJXp-mo" id="MJXp-Span-103" style="margin-left: 0em; margin-right: 0.222em;">,</span></span></span><script type="math/tex" id="MathJax-Element-3">this->assertEquals("Selenium Test Page", </script>this->getTitle());<br />   }<br /> }<br /> ?><br /> --------------------------------------------------------------------------------<br /> <br />  このテスト・ケースをphpunitコマンドで実行すると、Firefoxが起動し、指定したURLのページが表示されます。 <p> <a href="/sites/default/files/articles/1202-301.png" target="_blank"><img alt="" src="/sites/default/files/articles/1202-301.png" style="width: 400px; height: 201px;" /></a> </p> <h2> Selenium RCを使ったブラウザテストの自動化(2) </h2> <p>  アサーション・メソッドの実行結果は、コンソールで確認できます。この場合、</p><title>タグの 文字列が一致しないので、テストは失敗となりました。<br /> --------------------------------------------------------------------------------<br /> $ phpunit SeleniumTest.php<br /> PHPUnit 3.4.12 by Sebastian Bergmann.<br /> <br /> F<br /> <br /> Time: 8 seconds, Memory: 8.25Mb<br /> <br /> There was 1 failure:<br /> <br /> 1) SeleniumTest::testTitle<br /> Current URL: <a href="http://example.com/">http://example.com/</a><br /> <br /> Failed asserting that two strings are equal.<br /> --- Expected<br /> +++ Actual<br /> @@ @@<br /> -Selenium Test Page<br /> +Example Web Page<br /> <br /> /tmp/SeleniumTest.php:16<br /> <br /> FAILURES!<br /> Tests: 1, Assertions: 1, Failures: 1.<br /> --------------------------------------------------------------------------------<br /> <br />  より具体的なテスト・ケースの書き方は、PHPUnitのマニュアルを参照してください。<br /> <br />  最後に、テストだけではない、ちょっと違った利用例を紹介します。手前みそではありますが、私が開発したサービス「IOS SC」(<a href="http://www.pci-aios.jp/service/219">http://www.pci-aios.jp/service/219</a>) です。<br /> <br />  IOS SCは、Webサイトの品質を測定するASPサービスです。テスト・シナリオを定期的に自動実行し、画面の表示に必要とされた時間を測定/レポートする サービスです。測定結果が基準値を下回った場合にアラートのメールを送信することもできます。<br /> <br /> 【IOS SCの機能】<br /> (1)レスポンス時間の監視<br /> (2)アラート・メールの通知<br /> (3)テスト結果のレポート表示<br /> <br />  IOS SCも、PHPUnitのSelenium RC連携機能を内部で使っています。単なるWebサイトの死活監視にとどまらず、ユーザーがWebブラウザで行う操作を再現できます。例えば、ECサイト での「ログイン—商品検索—カート追加—カートからの削除—チェックアウト」までの流れを、定期的にテストすることが可能です。<br /> <br />  また、APIを持たないWebシステムに対して、一定の時間で決まった操作を行うことにも利用できます。例えば、ログイン後に日付を指定して売上データ をダウンロード/アップロードする作業も自動化できます。<br /> <br />  さて、4回にわたり、PHPUnitを使ったテストの自動化について紹介してきました。PHPUnitの持っている機能の一部しか紹介できませんでした が、参考にしていただけたら幸いです。ありがとうございました。
PCIアイオス株式会社

テンアートニ(現サイオステクノロジー)、ゼンド・ジャパンなどを経て現在、PCIアイオスに勤務。これまでに組込み開発からWebシステム開発まで幅広く開発を経験。現在は、オープンソースソフトウェアを利用したシステムの開発、運用、パフォーマンスチューニングを担当。特にLAMP(Linux/Apache/MySQL/PHP)環境を得意とする。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています