月曜日, 1月 29, 2007

[Testing][Perl]Test::WWW::SeleniumでPerlからSelenium RCを操作する

Selenium IDEを使うとhtmlの各種プログラミング言語用のテストコードも出力してくれます。
Perlの場合はTest::WWW::Seleniumモジュールを利用します。

前記事「Selenium RCとSelenium IDEでWEBアプリのUIテストを簡単自動化」の操作で出力されるPerlコードは以下のようになります。

Selenium IDEによって出力されるコード

use strict;
use warnings;
use Time::HiRes qw(sleep);
use Test::WWW::Selenium;
use Test::More "no_plan";
use Test::Exception;

my $sel = Test::WWW::Selenium->new( host => "localhost",
                                    port => 4444,
                                    browser => "*firefox",
                                    browser_url => "http://localhost:4444" );

$sel->open_ok("/");
$sel->type_ok("q", "Perl");
$sel->click_ok("btnG");
$sel->wait_for_page_to_load_ok("30000");
$sel->is_text_present_ok("Perl の検索結果");


スクリプトはutf8で保存します。browser_urlがlocalhostになってしまうようなのでwww.google.co.jpに変更します。

スクリプト実行前にSelenium RCを起動しておきます。
java -jar selenium-server.jar


続いてテストスクリプトを実行します。
ok 1 - open, /
ok 2 - type, q, Perl
ok 3 - click, btnG
ok 4 - wait_for_page_to_load, 30000
not ok 5 - is_text_present, Perl の検索結果
# Failed test 'is_text_present, Perl の検索結果'
# at test_utf8.t line 19.
1..5
# Looks like you failed 1 test of 5.

最後のテストで失敗してしまいました。verboseモードを使って実行手順をトレースしてみまると以下のようなコードを送信しているようでした。
---> Requesting http://localhost:4444/selenium-server/driver/?cmd=isTextPresent&1=Perl%20%C3%A3%C2%81%C2%AE%C3%A6%C2%A4%C2%9C%C3%A7%C2%B4%C2%A2%C3%A7%C2%B5%C2%90%C3%A6%C2%9E%C2%9C&sessionId=1170055300866
Got result: OK,false


日本語の部分がおかしいようなのでコードを追ってみると以下のようにutf8フラグ付を前提としているようです。
URI::Escape::uri_escape_utf8(shift @args);

テストスクリプトにuse utf8をつけて再度実行してみます。
ok 1 - open, /
ok 2 - type, q, Perl
ok 3 - click, btnG
ok 4 - wait_for_page_to_load, 30000
ok 5 - is_text_present, Perl の検索結果
1..5

上手く実行できました。

このようにプログラムからテストを実行すると、テストに必要なデータの準備から後始末までを自動化できます。例えばユーザーの新規登録フローをテストするといったケースでは登録しようとするユーザーのデータがないことが前提になりますので、まずテストユーザのデータを削除してからブラウザを起動して登録フローのテストを実行することで繰り返しテストすることが可能です。DBがトランザクションをサポートしているものであれば最後にrollbackしてしまえば不要なデータも残らず快適です。

このあたりのノウハウは前項でも紹介したWEB+DB Pressで詳しく紹介されています。Perlは無いですが。。。


最終的なコード
use strict;
use warnings;
use Time::HiRes qw(sleep);
use Test::WWW::Selenium;
use Test::More "no_plan";
use Test::Exception;
use utf8;

my $sel = Test::WWW::Selenium->new( host => "localhost",
                                    port => 4444,
                                    browser => "*firefox",
                                    browser_url => "http://www.google.co.jp",
                                     );

$sel->open_ok("/");
$sel->type_ok("q", "Perl");
$sel->click_ok("btnG");
$sel->wait_for_page_to_load_ok("30000");

$sel->is_text_present_ok("Perl の検索結果");


木曜日, 1月 18, 2007

[Perl]Benchmarkモジュールの小ネタ

PPerlのベンチを取りたくて、timeコマンドとかでも充分差は出るのですが、繰り返し実行される際の差を取りたかったのでBenchmarkモジュールでやろうとしたら少しおかしな結果になりました。

ちなみにPPerlとはスクリプトをコンパイルしてデーモンとして常駐させることで、起動時のオーバーヘッドをなくして高速化するというモジュールです。これもPerl Hacksに載ってます。

ベンチ対象(hello.pl)

use strict;
use Template;

my $tmpl = <<__TMPL__;
str = [% str %]
__TMPL__

my $tt = Template->new();
$tt->process(\$tmpl, { str => 'hello' });

print "hello\n"だけでやるとプロセス間通信のオーバーヘッドの方が大きいのかかえって遅くなったので、適当に大きめなモジュールを使えということでTemplateを使用。

で、ベンチのプログラムですが、以下のようにSYNOPSISどおりに実行すると
#!/usr/local/bin/perl
use strict;
use Benchmark qw(timethese);

timethese(100, {
    pperl => sub {
        my $ret = `/usr/bin/pperl -w --no-cleanup hello.pl`;
        die "error" unless ($ret =~ m|hello|);
    },
    perl => sub {
        my $ret = `/usr/bin/perl -w hello.pl`;
        die "error" unless ($ret =~ m|hello|);
    },
});


こんな感じになります。
Benchmark: timing 100 iterations of perl, pperl...
    perl: 42 wallclock secs ( 0.01 usr 0.03 sys + 38.64 cusr 2.56 csys = 41.24 CPU) @ 2500.00/s (n=100)
    pperl: 6 wallclock secs ( 0.01 usr 0.03 sys + 0.34 cusr 0.38 csys = 0.76 CPU) @ 2500.00/s (n=100)


wallclockとかはちゃんと出ているんですがRateがおかしい。これでも結果はわかるといえばわかるのですが気にいらないのでいろいろ調べていると、第3引数にstyleを指定することができるというのを見つけました。これを使うと親プロセスだけの時間とか子プロセスだけの時間とか計ってくれるようです。今回は子プロセスの実行時間が欲しいので'nop'としました。

スタイルの指定
#!/usr/local/bin/perl
use strict;
use Benchmark qw(timethese :hireswallclock);

timethese(100, {
    pperl => sub {
        my $ret = `/usr/bin/pperl -w --no-cleanup hello.pl`;
        die "error" unless ($ret =~ m|hello|);
    },
    perl => sub {
        my $ret = `/usr/bin/perl -w hello.pl`;
        die "error" unless ($ret =~ m|hello|);
    },
}, 'nop');


実行結果
Benchmark: timing 100 iterations of perl, pperl...
    perl: 47.9732 wallclock secs (44.66 cusr + 3.14 csys = 47.80 CPU) @ 2.09/s (n=100)
    pperl: 7.10064 wallclock secs ( 0.35 cusr + 0.47 csys = 0.82 CPU) @ 121.95/s (n=100)

うまく出ました。それにしてもallのとき(デフォルト)は親子の合計で/sも出してくれればいいのにと思うのは私だけでしょうか?

ちなみにこれを調べている過程で':hireswallclock'を見つけました。これをuseのときに指定しておけばwallclockもTime::HiResで出してくれます。ちょっと便利。




[Book][Perl]Perl Hacks日本語版

ついに日本語版が出るようです。



すぐに使えるものから、ちょっとマニアックなものまで、Perlプログラミングに関する101のTIPSが集められています。英語版を買って全部は読みきれていないのですが印象に残ったハックを紹介。

Hack 3 Perlドキュメントをオンラインでブラウズしよう
Pod::Webserverモジュールが紹介されています。
Pod::Webserver::Sourceも組み込むとソースコードも閲覧できて便利です。ただいずれも日本語が上手く表示できないのが難点。

Hack 27 反復子から複数の値を引き出そう
wantarrayよりも豊富なcontext情報を提供するWantモジュールが紹介されています。
リストの数や、HASHかどうかまで判定できたりまします。

Hack 47 メソッド引数の自動宣言
B::Deparseモジュールの応用例です。CODEREFからCODEテキストを取得してそれを置換処理して$selfを自動宣言するという力技です。

Hack 74 使われているすべてのモジュールをトレースしよう
Devel::TraceUse
モジュールのuse順をトレースしてくれます。
当ブログの[Perl]useなしでどこでもDump(@INCにオブジェクトを格納するパターン)はここで知った知識を使っています。
でもこのTraceUseモジュール、じつはrequireのところでエラーになっていて本当にrequireするのにかかった時間を計っているわけではないようです。バグな気がしたので直そうとしたのですがrequireのネストが上手く処理できず直そうとしても上手くいきませんでした。。。

Hack 78 サブルーチンの中身を覗こう
Bモジュールのsvref_2objectを使うとCODEREFからシンボル名などさまざまな情報が取得できます。Bモジュール面白いですね。

そのほかにもvimでパッケージ名(::も含めて)をコード補完したりだとかデバッガをカスタマイズしたりなど使えるマニアックなハックが満載です。

原著


水曜日, 1月 10, 2007

[Testing]Selenium RCとSelenium IDEでWEBアプリのUIテストを簡単自動化

WEB+DB PressでSelenium特集が掲載されていたので試してみました。



Seleniumとはブラウザからのテストを自動化するツールです。テストケースをHTMLのtableタグで記述するため、比較的簡単にテストケースを作成できます。Selenium-Coreのみではテストケースをテスト対象アプリケーションがあるサーバと同じ場所に置かなければならない為若干面倒ですが、Selenium RCを使うとアップロードが不要になる為非常に便利です。

インストール
Selenium Remote Control
現時点での最新バージョンは0.90ですが-htmlSuiteオプションが使えないとのことなのでバージョン0.81を使用します。解凍して適当なところに置いてください。

Selenium IDE
Firefoxエクステンションです。バージョン0.86を使用しました。

テストに必要なファイル
作成するファイルは以下のとおりになります。
TestCase.html ・・・ 個々のテストケース
TestSuite.html ・・・ テストケース一式をまとめたファイル。個々のテストケースへのリンクを貼る

少量のテストであればテキストエディタでも作成できますが、Selenium IDEを使うとブラウザの操作を記録してテストケースとなるhtmlファイルを作成してくれます。使い方はIDEを起動して記録ボタンを押した後、テスト対象となるアプリケーションを操作するだけです。

サンプル動画(Flash)
のように操作すると以下のようなhtmlファイルが生成できます。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>TestCase1</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">TestCase1</td></tr>
</thead><tbody>
<tr>
    <td>open</td>
    <td>/</td>
    <td></td>
</tr>
<tr>
    <td>type</td>
    <td>q</td>
    <td>Perl</td>
</tr>
<tr>
    <td>clickAndWait</td>
    <td>btnG</td>
    <td></td>
</tr>
<tr>
    <td>assertTextPresent</td>
    <td>Perl の検索結果</td>
    <td></td>
</tr>

</tbody></table>
</body>
</html>



続いてTestSuiteファイルを作成します。こちらはテストケースを一覧したhtmlとなります。

<html>
<head><title>Test Suite</title></head>
<body>
  <table cellpadding="1" cellspacing="1" border="1">
    <tbody>
      <tr><td><b>Test Suite</b></td></tr>
      <tr><td><a href="./TestCase1.html">TestCase1</a></td></tr>
    </tbody>
  </table>
</body>
</html>



テストの実行
Selenuim RCを起動してテストを実行します。
コマンドの構文は
java -jar selenium-server.jar -htmlSuite <ブラウザ> <テスト対象サイトURL> <テストスイートファイル> <テスト結果ファイル>

です。

上記TestSuiteの場合は以下のようにします。
java -jar selenium-server.jar -htmlSuite "*firefox" "http://www.google.co.jp" tests/TestSuite.html tests/result.html

とします。

以上で自動的にブラウザが起動してテストが実行され、結果がresult.htmlファイルに出力されます。

ただこのままですテストが終わると即ブラウザが終了してしまうため、いちいちresult.htmlを開いて見ないと結果がわからないのが面倒です。そこで
[Selenium]assertEvalの活用方法(ブレイクポイント、終了通知)
を参考にJavaScriptが使えることを利用してalertするテストケースを用意しておき、TestSuite.htmlの最後に追加してやると、テストの最後にalertが出るので即結果が確認できます。

テスト実行結果


Seleniumではブラウザを会してテストを行う為JavaScriptの実行結果もテストできます。Selenium RCを使うとコマンド一つでブラウザの起動からテスト結果の保存までが行えるので回帰テストが非常に楽になると思います。

参考URL
Selenium 0.7利用手順書(前編)
これはすごい! Web案件必須 Selenium - 人気急上昇中自動テストツール