Upload
takuya-tsuchida
View
3.272
Download
1
Embed Size (px)
Citation preview
- 1 -Public/公開情報
エレクトロニクス事業本部
2011/10/15
土田 拓也(Takuya Tsuchida)@tsucchi
id: tsucchi1022
あんなテスト・こんなテスト (This and That about testing)
- 2 -Public/公開情報
Abstract
テストの話をします
(I'll be talking about testing)
とくに「テストしにくい部分をどのようにテストするか」について話します
(Especially, I'll talk about how to test the part which is hard to test)
- 3 -Public/公開情報
About Me
土田 拓也(Takuya Tsuchida)
所属: 凸版印刷株式会社
エレクトロニクス事業本部 システム開発部(TOPPAN PRINTING Co., LTD
Electronics Division System Development team)
仕事: MES(製造実行システム)の開発・運用など(Develop and operate MES(Manufacturing Execution System))
– DB 設計したり、SQL 書いたり、Perl 書いたりしています(designing DB schema, writing SQL and Perl etc)
CPAN(PAUSE): TSUCCHI
id(hatena): tsucchi1022
twitter: @tsucchi
github: https://github.com/tsucchi
- 4 -Public/公開情報
testcodes
テストコード、書いてますか?
(Do You Write testcodes?)
テストコードとは(What is the testcode?)
– 「入力」と「その入力に対して、期待する出力」を書いて、一致するかどうかを検証するプログラム
(programs which validates 'input' and 'expected output from the provided input' are correct.)
- 5 -Public/公開情報
Automated Testing (2)
Example) testing add() subroutine
#!/usr/bin/perl -wuse strict;use warnings;
use Test::More;
sub add { my (@inputs) = @_; my $result = 0; for my $input ( @inputs ) { $result += $input; } return $result;}
# testing 'add' subroutineis( add(1, 2), 3 );is( add( (1 .. 10) ), 55 );
done_testing();
Subroutine to be tested
Input for test
Expected output
- 6 -Public/公開情報
Strong points and Weak Points
長所(strong points)
– 繰り返し実行できる(It enables to run any time)
• 改修やリファクタリングでエンバグしていないか容易に調べられる(You can easily find whether enbug or not when you
finished bug-fix or refactorings)
• Jenkins などの CI サーバと組み合わせることで、コミット時などの任意のタイミングでテストを実施できる(Combine with CI server, It is enable to run tests any time such as
after commit)
短所(weak points)
– イニシャルコストが上がる(increase initial costs)
– テストを書きにくい場合がある(Sometimes, It is hard to write testcodes)
- 7 -Public/公開情報
When It is hard to write testcode
「入力」や「出力」が明確ではなかったり、作りにくい場合(Inputs or outputs are ambiguous or hard to make these)
– 標準入出力(STDIN/STDOUT)
– コマンドラインオプション(commandline options)
– 時刻(system clock)
– DB
– etc.
これらを、「なんとかする」やり方を紹介します(I'll introduce how to deal with such things)
- 8 -Public/公開情報
標準入出力(STDIN/STDOUT)
コマンドラインオプション(commandline options)
時刻(system clock)
DB
etc.
Public/公開情報
Tests for STDIN/STDOUT
Principle
– 内部のロジックが良くテストされているなら、無理して実施する必要は無い(If internal logic is well tested, It is no need to test STDIN/STDOUT
forcefully)
Example Situation
– コマンドラインツールのテストをしたい(want to test command-line tool)
– 外部モジュールの中間出力が見たいが、その内容が標準出力を使っている(middle output for external modules, but the output is printed in
STDOUT/STDERR)
– warn/carp の内容を確認したい(want to check output by warn/carp)
Solution
– use IO::Scalar
– use Capture::Tiny(for STDOUT/STDERR)
– use IO::Capture::STDOUT/STDERR
– tie STDIN/STDOUT/STDERR
- 10 -Public/公開情報
Automated Testing (2)
Ex 1)using IO::Scalar and capture STDIN
#!/usr/bin/perl -wuse strict;use warnings;use Test::More;
sub add { my $result = 0; while( <STDIN> ) { chomp; $result += $_; } return $result;}
subtest 'add', sub { my $inputs = "1\n2\n3\n"; # input from STDIN open my $stdin_fh, '<', \$inputs; local *STDIN = *$stdin_fh; # replace default STDIN is( add(), 6 );};done_testing();
- 11 -Public/公開情報
Automated Testing (2)
Ex 2) using Capture::Tiny(for STDOUT/STDERR)
#!/usr/bin/perl -wuse strict;use warnings;use Test::More;use Capture::Tiny qw(capture);
sub add { my($a, $b) = @_; print $a + $b;}
my ($stdout) = capture { add(1, 2);};is($stdout, 3);done_testing();
– 簡単に使えるので、Test::Warn の代用とするのも良いと思う
(I think it's good idea to use this module alternate for Test::Warn)
- 12 -Public/公開情報
標準入出力(STDIN/STDOUT)
コマンドラインオプション(commandline options)
時刻(system clock)
DB
etc.
Public/公開情報
Tests for command-line args
Principle
– 内部のロジックが良くテストされているなら、無理して実施する必要は無い(If internal logic is well tested, It is no need to test STDIN/STDOUT forcefully)
Example Situation
– コマンドラインツールのテストをしたい(want to test command-line tool)
– GetOpt::* を使わず、自前でオプション解析しているのを直したい(want to fix because it has self-implemented command-line args analysis)
Solution
– @ARGV を書き換える
- 14 -Public/公開情報
Tests for command-line args
Ex) testing command-line args#!/usr/bin/perl -wuse strict;use warnings;use Test::More;our $a_str = undef;# 本当は別 package にある / in real case, this subroutine is defined in other packagesub read_args { while ( $_ = shift @ARGV ) { if ( $_ =~ /^-a$/ ) { $a_str = shift @ARGV; } # ... other option analyses are follows }}subtest 'a option with arg test', sub { $a_str = undef; local @ARGV = ("-a", "a_value"); #ここにオプションを指定 / passes options here read_args(); is($a_str, "a_value");};done_testing();
- 15 -Public/公開情報
標準入出力(STDIN/STDOUT)
コマンドラインオプション(commandline options)
時刻(system clock)
DB
etc.
Public/公開情報
Tests for system clock
Principle
– 時刻に依存せずテストが書かれるべき(Tests should be written in no
depencency to system clock)
Example Situation
– ログの日付フォーマットが正しいかチェックしたい(want to test
datetime format in logs)
– ロット番号など、日付によって処理内容が変わるものをテストしたい(want to test what changes depending on datetime such as lot-no)
Solution
– 時刻を改竄する(alter perl's system clock)
• use Test::MockTime
• use Time::Mock
• CORE::GLOBAL::time() を書き換える(override
CORE::GLOBAL::time)
- 17 -Public/公開情報
Tests for system clock
Ex) Test::MockTime
#!/usr/bin/perluse strict;use warnings;
BEGIN { $ENV{TZ} = 'JST' }
use Test::MockTime qw(set_fixed_time);use Test::More;use POSIX qw(strftime);
sub some_lot_no { return strftime("%Y%m%d-%H%M%S", localtime()); }
set_fixed_time('2009-03-23T11:22:33');is( some_lot_no(), '20090323-112233');done_testing();
- 18 -Public/公開情報
標準入出力(STDIN/STDOUT)
コマンドラインオプション(commandline options)
時刻(system clock)
DB
etc.
Public/公開情報
Tests for DB
Principle
– 基本はモック(DBD::Mock)を使うべき(Mock should be used)
– ビジネスロジックと DB は切り離すべき(Business logics and DB should be
separated)
Example Situation
– ストアドプロシージャをテストしたい(want to test stored procedure)
– ORM を使わず、生の DBI を使っているので SQL をテストしたい(want to test SQL because we don't use ORM)
Solution
– データを流し込む(load data into DB)
– Test::mysqld + something
• Test::Fixture::DBI
• Test::DBUnit• Test::DataLoader::MySQL
- 20 -Public/公開情報
標準入出力(STDIN/STDOUT)
コマンドラインオプション(commandline options)
時刻(system clock)
DB
etc.
- 21 -Public/公開情報
exit measures(1)
テスト中に exit が呼ばれると、意図せずテストが通ってしまう
(If exit() is called, tests are passed accidentally)
Example Situation
– 他人のコードを引き継いだ際(when takeover someone's code)
– ライブラリがエラー処理後に exit を呼んでいた(library routine calls
exit after error handling)
Solution
– exit()の上書き(override exit)
– exit を使っている関数/メソッドの上書き(override
subroutine/method which uses exit)
- 22 -Public/公開情報
Exit measures(2)
Ex1) exit() causes problem
% prove exit.texit.t .. ok All tests successful.Files=1, Tests=1, 0 wallclock secs ( 0.04 usr 0.00 sys + 0.01 cusr 0.01 csys = 0.06 CPU)Result: PASS
#!/usr/bin/perl -wuse strict;use warnings;use Test::More 'no_plan';my $important_value = '';sub evil_operation { $important_value = "aaa"; exit 0; }
ok(1);evil_operation();is( $important_value, '' );
– This test successes unexpectedly
- 23 -Public/公開情報
Exit measures(3)
Ex2) measured exit() call
% prove exit_measured.t exit_measured.t .. 1/? unexpected exit called! at exit_measured.t line 6....(snip)Result: FAIL
#!/usr/bin/perl -wuse strict;use warnings;use Test::More 'no_plan';BEGIN { *CORE::GLOBAL::exit = sub { die 'unexpected exit called!' } } # ADD THIS!my $important_value = '';
sub evil_operation { $important_value = "aaa"; exit 0; }
ok(1);evil_operation();is( $important_value, '' );
– It's OK. It should be failed.
- 24 -Public/公開情報
setUp/tearDown(1)
xUnit を使っていた人は setUp/tearDown が使いたいかも
(xUnit users may want to use setup/tearDown)
– それ Test::Class で出来るよ!(Test::Class enables it!)
#!/usr/bin/perl -wuse strict;use warnings;use Test::Class;
MyTest->runtests();
package MyTest;use parent qw(Test::Class);use Test::More;
sub set_up :Test(setup) { diag("setup"); }sub tear_down :Test(teardown) { diag("teardown"); }
sub my_test :Test(1) { ok(1); #this is some test }sub my_test2 :Test(1) { is("1", "1"); #this is another test }
- 25 -Public/公開情報
setUp/tearDown(2)
でも書き方が変わるのは面倒くさい
(But it isn't good that how to write test is changed)
– subtest + Hook::LexWrap#!/usr/bin/perl -wuse strict;use warnings;use Test::More;use Hook::LexWrap;
wrap 'subtest', pre => sub { diag("setup"); }, #alternate for setup post => sub { diag("teardown");} #alternate for teardown;
subtest 'my_test', sub { ok(1); };subtest 'my_test2', sub { is("1", "1"); };
done_testing();
- 26 -Public/公開情報
setUp/tearDown(3)
実行例(execution example)
% perl setup_teardown.t# setup ok 1 1..1ok 1 - my_test# teardown# setup ok 1 1..1ok 2 - my_test2# teardown1..2
setup called
test called
teardown called
- 27 -Public/公開情報
Caution(1)
今回紹介したテクニックをプロダクションコード側で使わない
(Don't use these techniques in production code)
– プロダクションコードでモンキーパッチしたり、時間や CORE::GLOBAL::* を書き換えたり、@ARGV 書き換えたり、標準入出力捕まえたりしないこと
(In production code, don't monkey-patch, don't alter system clock, don't replace CORE::GLOBAL::*, don't replace @ARGV, and don't capture STDIN/STDOUT)
– テストでは有効なテクニックでも、プロダクションコードで使うと妙なバグに振り回されるかもしれません
(These techniques are useful in testcode, but you may encounter curious bugs if using it in production code)
- 28 -Public/公開情報
Caution(2)
「テストしにくい部分を何とかしたい!」という考えは基本的には何かが間違っています
(I think it is wrong opinion such as 'I want to manage testcode which is hard to be written')
– 段階的に直していくべき(It should be fixed gradually)
– Mock 使うとか、他の部分をテストしてカバーするとか(using Mock or tests other parts to cover it)
- 29 -Public/公開情報
Conclusion
テストしにくいものも、Perl だと結構なんとかなります
(Sometimes it is hard to write testcode, but Perl provides power to make it possible)
「どうやったら、このテストしにくいコードを何とかできるか」を考えるのは結構楽しい
(It is fun thinking about how to write testcode which is hard to be written)
– とくにレガシーコードを相手にする場合は(especially for legacy codes)
テストを書きましょう!辛いテストでも Perl なら何とかなります!
(Let's write testcode! Perl enables you to provide power to write hard tests)
- 30 -Public/公開情報
- 31 -Public/公開情報
質疑をうけて、ちょっとだけ補足(発表後に追記)
exit で意図せずテストが成功する場合の話ですが、これは「no_plan」にしているときのみ発生する事象です。
比較的新しい Test::More を使っていれば、done_testing() が使えるので、それを使うべきです。また、5.8.8 とかに標準添付される Test::More だと done_testing()が使えないバージョンなので、その場合は no_plan をそもそも避けるべきです。
(sorry only in Japanese)