管理者の重要な仕事のひとつに、
テストの自動化
安定したシステムにテストは欠かせませんが、
以前ファイアーウォールの移行作業をしたのですが、
さて、
いろいろあったものの、
本題に戻ってテストの自動化です。人為的ミスという言葉がありますが、
なぜPerlなのか
以前は、
Perlの最大の強みのひとつは、
CPANで公開されているモジュールを再利用するメリットはそれだけではありません。こうしたモジュールは不特定多数によってさまざまな環境で使われますので、
CPANモジュールの使いかた
CPANで公開されているモジュールは、cpan
というCPANのパッケージ管理システムでインストールすることができます。ただし、g-cpan
というコマンドで、
インストールしたモジュールのマニュアルはperldoc
コマンドで参照できます。Foo::Bar
というモジュールならperldoc Foo::Bar
で参照できます。モジュールのソースコードもperldoc -m Foo::Bar
といったように、
モジュール紹介
管理者にとって便利なモジュールをいくつか紹介します。
- Net::Ping, Net::Ping::External
- ホストの到達性をテストするモジュールです。ICMPだけでなく、
TCPやUDPを使うテストも可能です。 - Net::DNS, Net::LDAP, Net::SMTP, Mail::POP3Client, libwww-perl
- それぞれ、
各種アプリケーション層のプロトコルを利用するためのモジュールです。 - Net::DHCP, Net::BGP, Net::SNMP
- L2/
L3機器を監視したり、 保持しているデータを処理するのに便利なモジュールです。 - Net::IP, Net::Netmask, NetAddr::IP, Net::MAC::Vendor
- IPアドレスの処理や操作には欠かせないモジュールです。
- Net::Telnet, Net::SSH, Net::SSH::Expect, Net::SSH::Perl
- 実際にコンソールにアクセスしなければできない操作をPerlから実行するのに便利なモジュールです。
- Net::Pcap, Net::Packet
- より低レベルのネットワーク通信にアクセスを提供するモジュールです。
- Nmap::Parser, Nagios::Object, POE::Filter::Snort
- 管理者におなじみのツールにPerlでアクセスできるモジュールです。
いかがでしょう。少しの想像力とこうした強力なモジュールがあれば、
初めてのテスト
ここではPerlの文法などの解説はしませんが、
シンプルなテストを作成してみましょう。ここでは、Test::Simple
を使います。まず、
> mkdir mytest > cd mytest > mkdir t > vim t/01.t #!/usr/bin/perl use strict; use warnings; use Test::Simple tests => 1; ok( 1 + 1 == 2, '1 + 1 = 2');
さっそく実行してみましょう。
> perl t/01.t 1..1 ok 1 - 1 + 1 = 2
テストはパスしたようです。では、
use Test::Simple tests => 1;
Test::Simple
というモジュールを使い、
ok( 1 + 1 == 2, '1 + 1 = 2');
ok()
はTest::Simple
の関数で、ok( EXPR, $description )
のEXPR
が真であればテストが成功したとみなし、
次に、
> vim t/02.t #!/usr/bin/perl use strict; use warnings; use Test::Simple tests => 1; sub is_integer { my ($number) = @_; if ( $number =~ qr/^\d+/ ) { return 1; } else { return; } } ok( is_integer('1'), '1 is integer' );
is_
がテスト対象の関数です。正規表現を使い、
> perl t/01.t 1..1 ok 1 is integer
これだけではテストとして不十分ですので、
ok( is_integer('1000'), '1000 is integer' ); ok( !is_integer('ABC'), 'ABC is not integer' ); ok( !is_integer('1.1'), '1.1 is not integer' );
テストを追加したぶんだけテストプランの数も、use Test::Simple tests => 4;
というように増やします。実行するとどうなるでしょうか。
> perl t/02.t 1..4 ok 1 - 1 is integer ok 2 - 1000 is integer ok 3 - ABC is not integer not ok 4 - 1.1 is not integer # Failed test '1.1 is not integer' # at t/01.t line 20. # Looks like you failed 1 test of 4.
最後のテストが失敗しています。1.
は整数ではないのに真が返っています。原因は次の部分にありました。
if ( $number =~ qr/^\d+/ ) { return 1; } else { return; }
正規表現で先頭から1個以上の数字が連続する場合に真を返していますが、"1.
や"111foo"
にもマッチしてしまいます。正しくは、
if ( $number =~ qr/^\d+$/ ) { ...
修正したテストを実行すると、
> perl t/02.t 1..4 ok 1 - 1 is integer ok 2 - 1000 is integer ok 3 - ABC is not integer ok 4 - 1.1 is not integer
複数のテストをまとめて実行する
テストが増えてくると、prove
です。
> prove t t/01....ok t/02....ok All tests successful. Files=2, Tests=5, 0 wallclock secs ( 0.04 cusr + 0.01 csys = 0.05 CPU)
すべてのテストがパスした場合は、
t/01....ok t/02....ok 1/4 # Failed test '1.1 is not integer' # at t/02.t line 20. t/02....NOK 4/4# Looks like you failed 1 test of 4. t/02....dubious Test returned status 1 (wstat 256, 0x100) DIED. FAILED test 4 Failed 1/4 tests, 75.00% okay Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/02.t 1 256 4 1 4 Failed 1/2 test scripts. 1/5 subtests failed. Files=2, Tests=5, 0 wallclock secs ( 0.05 cusr + 0.00 csys = 0.05 CPU) Failed 1/2 test programs. 1/5 subtests failed.
テストが失敗した場合は、
モジュールを使う
ネットワークに関連するモジュールを実際に使ったプログラムを書いてみましょう。ここでは、Net::Ping::External
を使います。cpan
もしくはお使いのパッケージ管理システムでNet::Ping::External
をインストールします。
Net::Ping::External
の使いかたは簡単です。以下のプログラムをt/
> vim t/ping.pl #!/usr/bin/perl use strict; use warnings; use Net::Ping::External qw{ ping }; # Net::Ping::Externalのping()を使う my $host = '127.0.0.1'; # 対象のIPアドレス my $result = ping( host => $host ); # pingを実行 if ( $result ) { # 結果が真なら print "$host is alive\n"; # 返事あり }
実に簡単ですね。実行してみます。
> perl t/ping.pl 127.0.0.1 is alive
モジュールのマニュアルはperldoc
で参照できます。タイムアウトやリクエストの数などを指定することも可能です。詳しくはperldoc Net::Ping::External
を参照してください。
モジュールを使ってテストを作る
先ほどのサンプルはまだテストとは言えません。Test::Simple
を使って、ping()
はホストから返事があれば真、ok()
で使えます。以下のプログラムを
#!/usr/bin/perl # ping01.t use strict; use warnings; use Test::Simple tests => 1; use Net::Ping::External qw{ ping }; my $host = '127.0.0.1'; ok( ping( host => $host ), "$host is alive" );
複数のホストをテストするのも簡単です。
#!/usr/bin/perl # ping02.t use strict; use warnings; use Test::Simple tests => 2; use Net::Ping::External qw{ ping }; my @hosts = qw( 127.0.0.1 192.168.0.1 ); for my $host ( @hosts ) { ok( ping( host => $host ), "$host is alive" ); }
テストモジュールを作る
ここまでで、
シンプルなテストであれば、
初めてのテストモジュール
ここでは、ping
によるテストを例にして、Test::Builder
モジュールが、
> mkdir -p t/lib/Mydomain/Test/Net/Ping > vim t/lib/Mydomain/Test/Net/Ping/External.pm use strict; use warnings; use base 'Exporter'; our @EXPORT = qw{ ping_ok }; use Test::Builder; my $test = Test::Builder->new; use Net::Ping::External qw{ ping }; sub ping_ok { my ( $ip, $desc ) = @_; return ( $test->ok( ping( host => $ip), $desc ) || $test->diag("\t$ip doesn't respond") ); } 1;
順番に解説します。
use base 'Exporter'; our @EXPORT = qw{ ping_ok };
これはExporter
モジュールを使い、ping_
という関数をあたかもそのプログラムで定義されているかのように呼び出すためのおまじないです。
use Test::Builder; my $test = Test::Builder->new;
ここでTest::Builder
モジュールを使い、Test::Builder
オブジェクトを作成しています。
use Net::Ping::External qw{ ping };
先程のテストと同じように、Net::Ping::External
を使います。
sub ping_ok { my ( $ip, $desc ) = @_; return ( $test->ok( ping( host => $ip), $desc ) || $test->diag( "\t$ip doesn't respond" ) ); }
これがこのモジュールのキモです。ping_
は、ping( host => $ip )
)ping_
の引数は、
return ( $test->ok( EXPR, $desc ) || $test->diag(@mesg) );
がひとつのイディオムです。EXPR
には真偽値を返すテスト条件を書きます。
では、
#!/usr/bin/perl use strict; use warnings; use Test::More tests => 1; use lib 't/lib'; use Mydomain::Test::Net::Ping::External; ping_ok('127.0.0.1', '127.0.0.1 is alive');
use lib
で、use Mydomain::Test::Net::Ping::External;
でテストに使用するモジュールを指定しています。
実行してみましょう。
> perl t/ping02.t 1..1 ok 1 - 127.0.0.1 is alive
失敗した場合は次のように出力されます。
> perl t/ping02.t 1..1 not ok 1 # Failed test at t/ping02.t line 9. # 127.0.0.2 doesn't respond # Looks like you failed 1 test of 1.
このようにTest::Builder
を使って作成したテストモジュールは、Test::*
モジュールと共存できます。例えば、prove
を実行してもきちんと表示されます。
> prove t t/01........ok t/02........ok t/ping01....ok t/ping02....ok All tests successful. Files=4, Tests=7, 0 wallclock secs ( 0.09 cusr + 0.02 csys = 0.11 CPU)
Test::Builder
の詳しい説明はperldoc Test::Builder
を参照してください。
これで、
さらなるテスト
今回はpingを対象にテストを作成しました。けれども、
- ユーザがログインできて、
期待する反応が返ってくるか (複数の処理のテスト) - 外部からメールを送信したら、
宛先のメールボックスに配送されて、 そのメッセージを取り出せるか (複数のサービスのテスト) - 許可されたユーザが許可されているサービスにアクセスでき、
そうではないユーザはアクセスできないか (ACLの確認) - 異常な入力やアクセスにも適切に対応できるか
(egress filtering/ open relay/ open proxy/ open DNS server) - 過去に起きたミスが繰り返されていないか
(ミスのテストケース化)
こうしたテストも今回作成したテストの延長線上にあります。また、
まとめ
Perlは管理者にとって有用なツールです。これまでに多くの管理者によって使われており、
まずは既存のテストを見直して、
今回紹介したのはソフトウェア開発向けに作られたテストのフレームワークですが、