A Perl Testing Tutorial
chromatic (http://wgz.org/chromatic/)
コードが仕様どおりの動きをすることを確認する。
o 実装忘れの防止
o アクシデントに対する動き
o 全てのテストにパスすれば完成
境界条件のチェック
o 不正な入力をどう扱うか
o イジワルな操作にどう対処するのか
prevent (known) regressions
o テストしていない機能はとつぜん動作しなくなるかもしれない
o 修正されていないバグは再発しうる
バグ報告をよりよいものにする
o テストの出力は要所をついたエラーであること
o 客先で簡単に発生(再現・追試)できること
* 「これを動かした結果を送ってください」
リファクタリングを可能にする
o you break it, you know it right away
o 副作用が明らかになっている
o well-tested code tends to be more modular
* 以前これと似た動作のテストをしたモジュールを作ったぞ。
それらは単に表面上の関係があるだけだ。2回目のテストは
ほとんど同じだから、それを含んだ親のクラスが必要だ。
既存のテストのほとんどを親クラスに移すと、それを継承する
クラスはもっとシンプルでとても短くなる。それらのテストも
さらにシンプルだ。これはメンテするコードが少なくなって、
書くコードも少ないし、新しい機能を追加する際の柔軟性も高
まる。
API を改善する
o テストするのが難しいコードは使うのも難しい
o テストコードを最初に書くことで、コーディングを始める前に
インターフェースを使う(考える)ことになる
* インターフェースがシンプルであればテストも楽
* 小さな関数は再利用が楽
自動化を進める
o easy to test code is usually well-decoupled
o easy to test is, by definition, scriptable
メンテを楽にする
o テストはセーフティーネット(転ばぬ先の杖?)である
* 今わからなくても、あとでもう一度繰り返します
o よくできたテストは短いドキュメントにもなる
* ソフトウェアがどんな仕様をもつべきかをデモする
* コードがどうふるまうべきかをデモする
恐れないで!
o 良いテストコードを書くことは良いコードを書く練習になる
o コードが書ければテストも書けるよね
o いいコードが書ければ、良いテストを書くことも教えられている
わけだ
テストは精神的にもいい
o テストコードを書くこと(とりわけテストファースト)は、テス
トを書きやすくする(コードを書きやすくするの間違いか?)
o よくテストされたコードならハックしてもおもしろい
o テストファースト・プログラミングはすばやいフィードバックが
可能。
o そしてそれはたいてい、前進的なフィードバックだ
Perl はそれを簡単にする
o ExtUtils::MakeMaker とテストされるもの
o Test::* モジュールは Test::Harness が必要とするものを隠して
(援助して)くれる
o if lasy dopes like us can do it, you can too!
"ok" について
print "ok";
print "not ok";
o 全てのテストの基礎
o シンプルな二値の条件
o 通常、連番が振られる
さらに "ok" について
o なぜ print することがムダなのか
* サブルーチンを使うのと同じく、同じ引数(コードの二重化)
* 手動で番号を振る
* 自分でやらないと診断メッセージが出ない
なにがマッチしたのか
何がマッチしなかったのか
どのテストがどこで失敗したのか
* もっと複雑な構成に対する組み込みの手続きがない
* メンテが大変
ok() 関数
ok(1==1);
o Test::Simple, Test::More, Test::Builder の基礎
o シンプルな2値の条件
o なぜそれがいいのか
* 同じコードを書く手間が省ける
* テストに名前がつけられる
* テストに説明を加えることでもっとクリアになる
* ポータビリティの心配がなくなる
テストを計画する
o テストのエラーの過不足を把握する助けになる
o どのテストが失敗したかを見つけやすくする
o Test::Harness に必要
use Test::Simple tests => 10;
use Test::Simple 'no_plan';
tests => n
o Test::Harness 用に 1..n の番号を出力する
o テストコードが完成したときに定義すべき
no_plan
o 1 から実際のテストの終わりまで連番が出力される
o どれだけのテストがあるかわからないとき
o モジュールが開発中のときに便利
o モジュールを配布するときは便利ではない
ok()
o 引数はスカラー
o 最初の引数はつねに真か偽
o 2番目の引数はテストの名前でオプショナル
o 条件はテストの名前に対応したコード
o テストの名前は条件を表す宣言になっていること
ok() が成功したとき
ok( 1, '1 should be true' );
ok 1 - 1 should be true
ok() が失敗したとき
ok( 0, '0 should be true' );
not ok 2 - 0 should be true
# Failed test (demotest.pl at line 17)
成功はグレーゾーンではない(明らかなテスト)
o 成功または失敗だけで、多分はない
o もっとも小さな単位でテストせよ
o 一度にひとつだけテストせよ
o 静かに成功させる(成功時は余計なメッセージが出なくなる?)
* エラーをなくす
* 警告をなくす
* そうすべきなら、エラーや警告のためのテストもすること
成功は全て成功か、全て失敗かのどちらか(test suite)
o 予想される失敗を全てチェックする
* undermine confidence in the code
* clutter up screens and logs
* hide new problems
o 変更を小さく保つことで、ダメージを局所にとどめる
o テストしつづけることで、ダメージを局所にとどめる
o プラットフォームに依存したテストを別にする方法がある
catching regressions
o バグの多くは新しいものではなく、繰り返し起こっていた
o なぜそれがまた起こったのか
o 新しいバグが出たら、それに対するテストを書く
テストファースト
o teeny, tiny, mini-iterations
o それぞれの仕事を二値の問題に分ける
o 「次に必要な機能は何だろう?」
* 全体の仕事の中の最も小さな要素
* 一度にひとつの小さな段階
テストファーストに対する二つの疑問
o 「この機能が働いていることをどうやって証明する?」
* その機能が働いていないと失敗するであろう、もっとも
シンプルなテスト
* テストは失敗しなければならない
o 「そのテストをパスするのに書かなければならない最小のコードは?」
* テストがシンプルなほど、必要なコードもシンプルになる
* テストは今成功しなければならない
o この手続きは既知の良いコードと包括的な test suite を生み出す
o 機能を全部作り終えた後、全体の test suite を試すのを忘れないこと
テストできるようなコードを書く
o 単位が小さいほど良い
o 単位がシンプルなほど良い
o test suite が良ければ、リファクタリングも簡単
o テストを最初に書けばそれができるようになる!
テストの配布
o Test.pm なら配布が楽
* ひとつのモジュールで、Test::Builder に依存しない
* Test::More の API とコンパチ
o Test::Simple suite はモジュールと一緒にされる
* CGI.pm などがこれをやっている
o Test::Simple は 5.8 に同梱
o モジュールは Test::Simple に依存できる
Test::Simple を卒業して Test::More へ、あるいは
なぜ ok() は不十分なのか
o それが成功か失敗かであるにしても、データは 1bit だけ
o デバッグのための情報が少ない
* 何が得られたのか?
* 何を受け取ったのか?
* "add a print at line 43" なんていう E-mail を本当に
受け取りたいと思ってるの?
* 真の横着は良いテストの兆候
is() - 一致
o 「これら二つは同じか」を調べる
o 型を考えてくれる
o undef の場合
is() が成功したとき
is( 2 + 2, 4, 'Two plus two should be four' );
ok 1 - Two plus two should be four
is() が失敗したとき
is( 2 + 2, 5, 'Two plus two should be five' );
not ok 2 - Two plus two should be five
# Failed test (demotest.pl at line 8)
# got: '4'
# expected: '5'
isnt - 不一致
o 「これら二つは違っているか」を調べる
o is() の反対
isnt() が成功したとき
isnt( 2 + 2, 5, 'Two plus two should not be five' );
ok 3 - Two plus two should not be five
isnt() が失敗したとき
isnt( 2 + 2, 4, 'Two plus two should not be four' );
not ok 4 - Two plus two should not be four
# Failed test (demotest.pl at line 10)
# '4'
# ne
# '4'
1..4
like() - 一致
o 文字列に対して正規表現を試す
o 最初の引数は文字列
o 二番目の引数は正規表現
* Perl のバージョンによっては qr// でコンパイル済み
正規表現
* そうでなければ正規表現を表す /foo/ のような文字列
like() が成功したとき
like( 'foobar', qr/fo+/,
'+ quantifier should match one or more' );
ok 5 - + quantifier should match one or more
like() が失敗したとき
like( 'fbar', qr/fo+/,
'+ quantifier should match zero or more' );
not ok 6 - + quantifier should match zero or more
# Failed test (demotest.pl at line 12)
# 'fbar'
# doesn't match '(?-xism:fo+)'
unlike() - 不一致
o like() の反対
o 使い方は同じ
unlike() が成功したとき
unlike( 'foobar', qr/baz/, 'foobar should not match baz' );
ok 7 - foobar should not match baz
unlike() が失敗したとき
unlike( 'foobar', qr/bar/, 'foobar should not match bar' );
not ok 8 - foobar should not match bar
# Failed test (demotest.pl at line 14)
# 'foobar'
# matches '(?-xism:bar)'
1..8
use-ok() の動作
o (これらは全てそれ自身のテストの名前を出力します)
o モジュールがコンパイルされて、import に成功したかを調べる
o モジュールをロードして、import() を適用する
o コンパイル時ではなく実行時(ランタイム)に動作する
o デフォルトのテスト名を出力するので、名前を指定する必要はない
o import 時の引数も渡せる
use_ok() が成功したとき
use_ok( 'Test::More' );
ok 11 - use Test::More;
use_ok() が失敗したとき
use_ok( 'No::Module' );
not ok 12 - use No::Module;
# Failed test (demotest.pl at line 20)
# Tried to use 'No::Module'.
# Error: Can't locate No/Module.pm in @INC (@INC
# contains: /usr/lib/perl5/5.6.1/i386-linux
# /usr/lib/perl5/5.6.1
# /usr/lib/perl5/site_perl/5.6.1/i386-linux
# /usr/lib/perl5/site_perl/5.6.1
# /usr/lib/perl5/site_perl .) at (eval 6) line 2.
require_ok の動作
o モジュールがコンパイルされたかどかを調べる
o モジュールをロードするが、import() はしない
o これもデフォルトのテスト名を出力する
require_ok() が成功したとき
require_ok( 'Test::Simple' );
ok 13 - require Test::Simple;
require_ok() が失敗したとき
require_ok( 'No::Module' );
not ok 14 - require No::Module;
# Failed test (demotest.pl at line 23)
# Tried to require 'No::Module'.
# Error: Can't locate No/Module.pm in @INC (@INC
# contains: /usr/lib/perl5/5.6.1/i386-linux
# /usr/lib/perl5/5.6.1
# /usr/lib/perl5/site_perl/5.6.1/i386-linux
# /usr/lib/perl5/site_perl/5.6.1
# /usr/lib/perl5/site_perl .) at (eval 8) line 2.
isa_ok() の動作
o オブジェクトがリファレンスかどうかを調べる
o オブジェクトになる
* ただしクラスではない
* コンストラクタに関する三つのテストをやってくれる
何かしら定義済みの値を返したか?
それはリファレンスか?
そのリファレンスはオブジェクトか?
o respects inheritance
o リファレンスになる(スカラー,アレー,ハッシュ,io,
コード)(io ってファイルハンドルのこと?)
isa_ok() が成功したとき
isa_ok( [], 'ARRAY' );
ok 15 - The object isa ARRAY
isa_ok() が失敗したとき
isa_ok( {}, 'IO::Socket' );
not ok 16 - The object isa IO::Socket
# Failed test (demotest.pl at line 26)
# The object isn't a 'IO::Socket' it's a 'HASH'
can_ok の動作
o オブジェクトが名前づけされた動作をもっているかを調べる(いみふめ)
o オブジェクトまたはクラスになる
o 一度のテストで、複数の関数やメソッド名を渡す
o 複数のテストでは、それらを全部テストするように
can_ok() の呼び出しを行う
o respects inheritance, but falls afoul of AUTOLOAD() just
like UNIVERSAL::can() does
can_ok() が成功したとき
can_ok( 'Test::More', 'can_ok' );
ok 17 - Test::More->can('can_ok')
can_ok() が失敗したとき
can_ok( 'Test::More', 'autotest' );
not ok 18 - Test::More->can('autotest')
# Failed test (demotest.pl at line 29)
# Test::More->can('autotest') failed
skip() - 制御する
o 指定した番号のテストをスキップする
o 通常、絶対に成功しないようなテストに対して使う
o スキップする理由と番号を指定する
o ブロックには SKIP というラベルをつける
o スキップするための条件が必要
skip() の動作
SKIP: {
skip( 'Never on a Sunday', 2 ) if (localtime)[6] == 0;
can_ok( 'Perl::Porter', 'buy_beer' );
can_ok( 'Perl::Porter', 'wear_short_pants' );
}
ok 21 # skip Never on a Sunday
ok 22 # skip Never on a Sunday
todo() - 制御
o 指定した番号のテストは失敗することを明記する
o 通常、失敗したときに使う
* 奮闘中!
* まだ実装が完全に終わってないとき
* バグがとりきれてないとき
* don't let these add up: they undermine the suite
o ブロックには TODO というラベルをつける
o 局所変数 $TODO に名前を設定
todo() の動作
TODO: {
local $TODO = 'The alien overlords have not arrived';
is( $smallpox->status(), 'ready', 'Smallpox ready' );
ok( @cow_decoys > 1000, 'Bait ready...' );
}
not ok 23 - Smallpox ready # TODO The alien overlords have
not arrived
# Failed (TODO) test (demotest.pl at line 46)
# got: 'replenishing'
# expected: 'ready'
not ok 24 - Bait ready... # TODO The alien overlords have
not arrived
# Failed (TODO) test (demotest.pl at line 47)
diag() - 制御
o 診断メッセージを表示する
o テストハーネスに影響を与えないことを保証
o 連番も成功も失敗も出力しない
o トリッキーなテストにコメントをつけるのに便利
diag() の動作
diag( "Now that you know a bit about test writing,\n",
"you have no excuse not to test." );
# Now that you know a bit about test writing,
# you have no excuse not to test.
pass() - 制御
o 無条件に成功したテストを出力する
o テストの名前を与える -- that's it
pass() の動作
pass( 'Executive fiat should work' );
ok 19 - Executive fiat should work
fail() - 制御
o 無条件に失敗したテストを出力する
o これもテストの名前を与える -- that's it
fail() の動作
fail( '... but we live in a meritocracy' );
not ok 20 - ... but we live in a meritocracy
# Failed test (demotest.pl at line 32)
のほうはもうすこしおもしろい。(英語 100ページ)