管理者の重要な仕事のひとつに、
テストの自動化
安定したシステムにテストは欠かせませんが、
以前ファイアーウォールの移行作業をしたのですが、
さて、
いろいろあったものの、
本題に戻ってテストの自動化です。人為的ミスという言葉がありますが、
なぜ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は管理者にとって有用なツールです。これまでに多くの管理者によって使われており、
まずは既存のテストを見直して、
今回紹介したのはソフトウェア開発向けに作られたテストのフレームワークですが、