`test::nginx` の知られざる使い方
API7.ai
November 24, 2022
前回の2つの記事で、test::nginx
のほとんどの使い方をマスターし、OpenRestyプロジェクトのテストケースセットの大部分を理解できるようになったと思います。これは、OpenRestyとその周辺ライブラリを学ぶには十分です。
しかし、OpenRestyのコード貢献者になりたい場合や、プロジェクトでtest::nginx
を使用してテストケースを書く場合、より高度で複雑な使い方を学ぶ必要があります。
今日の記事は、おそらくこのシリーズの中で最も「不人気」な部分になるでしょう。なぜなら、これまで誰も共有したことがない内容だからです。OpenRestyのコアモジュールであるlua-nginx-module
を例にとると、世界中に70人以上の貢献者がいますが、すべての貢献者がテストケースを書いているわけではありません。ですから、今日の記事を読めば、test::nginx
に対する理解が世界トップ100に入るでしょう。
テスト中のデバッグ
まず、開発者が通常のデバッグで使用する最もシンプルでよく使われるセクションを見ていきましょう。ここでは、これらのデバッグ関連のセクションの使用シナリオを一つずつ紹介します。
ONLY
多くの場合、元のテストケースセットに新しいテストケースを追加します。テストファイルに多くのテストケースが含まれている場合、実行に時間がかかります。特に、テストケースを繰り返し修正する必要がある場合です。
では、指定したテストケースのうち1つだけを実行する方法はあるでしょうか?これはONLY
セクションで簡単に実現できます。
=== TEST 1: sanity
=== TEST 2: get
--- ONLY
上記の疑似コードは、このセクションの使い方を示しています。単独で実行したいテストケースの最後の行に--- ONLY
を置くと、prove
を使ってテストケースファイルを実行する際に、他のすべてのテストケースが無視され、このテストだけが実行されます。
ただし、これはデバッグ中にのみ適しています。そのため、prove
コマンドがONLY
セクションを見つけると、コードをコミットする際にそれを削除することを忘れないように促します。
SKIP
1つのテストケースだけを実行する要件に対応して、特定のテストケースを無視する要件もあります。SKIP
セクションは、通常、まだ実装されていない機能をテストするために使用されます。
=== TEST 1: sanity
=== TEST 2: get
--- SKIP
この疑似コードからわかるように、その使い方はONLY
と似ています。テスト駆動開発を行っているため、まずテストケースを書く必要があります。そして、実装を共同でコーディングする際に、実装の難易度や優先度によって機能の実装を遅らせる必要がある場合があります。その場合、対応するテストケースセットをスキップし、実装が完了したらSKIP
セクションを削除します。
LAST
もう一つの一般的なセクションはLAST
です。これも使い方は簡単で、それ以前のテストケースは実行され、それ以降のテストケースは無視されます。
=== TEST 1: sanity
=== TEST 2: get
--- LAST
=== TEST 3: set
ONLY
とSKIP
の重要性は理解できますが、LAST
の用途は何でしょうか?実際、テストケースに依存関係がある場合、後続のテストが意味を持つためには最初のいくつかのテストケースを実行する必要があります。そのため、このような場合、LAST
はデバッグを続ける際に非常に役立ちます。
plan
test::nginx
の機能の中で、plan
は最もイライラさせられ、理解しにくいものの一つです。これはPerlのTest::Plan
モジュールに由来しており、そのドキュメントはtest::nginx
には含まれておらず、説明を見つけるのは容易ではありません。そのため、早い段階で紹介します。OpenRestyのコード貢献者の中には、この落とし穴にハマって抜け出せなかった人も何人かいます。
以下は、公式のOpenRestyテストセットの各ファイルの先頭で見られる類似の設定の例です。
plan tests => repeat_each() * (3 * blocks());
ここでのplan
の意味は、テストファイル全体で計画されたテストの数です。最終的な実行結果が計画と一致しない場合、テストは失敗します。
この例では、repeat_each
の値が2
で、テストケースが10個ある場合、plan
の値は2 x 3 x 10 = 60
になります。唯一混乱する可能性があるのは、数字3
の意味です。これはまるで魔法の数字のようです!
心配しないでください。例を見続ければ、すぐに理解できるでしょう。まず、以下のテストケースでplan
の正しい値は何かわかりますか?
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
ngx.say("hello")
}
}
--- request
GET /t
--- response_body
hello
おそらく、plan = 1
と結論づけるでしょう。なぜなら、テストはresponse_body
のみをチェックするからです。
しかし、そうではありません!正しい答えはplan = 2
です。なぜでしょうか?test::nginx
には暗黙のチェック、つまり--- error_code: 200
があり、デフォルトでHTTPレスポンスコードが200
であるかどうかを検出します。
したがって、上記の魔法の数字3
は、各テストが明示的に2回チェックされることを意味します。例えば、body
とerror log
に対して、そして暗黙的にresponse code
に対してです。
これが非常に間違いやすいため、以下の方法でplan
をオフにすることをお勧めします。
use Test::Nginx::Socket 'no_plan';
オフにできない場合、例えば公式のOpenRestyテストセットで不正確なplan
に遭遇した場合、原因を深く追求せず、単にplan
の式に数字を足したり引いたりすることをお勧めします。
plan tests => repeat_each() * (3 * blocks()) + 2;
これも公式に使用される方法です。
プリプロセッサ
同じテストファイルの異なるテストケース間で、いくつかの共通の設定がある場合があります。各テストケースで設定を繰り返すと、コードが冗長になり、後で修正するのが面倒になります。
このような場合、add_block_preprocessor
ディレクティブを使用して、Perlコードを追加できます。例えば以下のようになります。
add_block_preprocessor(sub {
my $block = shift;
if (!defined $block->config) {
$block->set_value("config", <<'_END_');
location = /t {
echo $arg_a;
}
_END_
}
});
このプリプロセッサは、すべてのテストケースにconfig
セクションを追加し、内容はlocation /t
です。これにより、後のテストケースでconfig
を省略して直接アクセスできます。
=== TEST 1:
--- request
GET /t?a=3
--- response_body
3
=== TEST 2:
--- request
GET /t?a=blah
--- response_body
blah
カスタム関数
プリプロセッサにPerlコードを追加するだけでなく、run_tests
関数の前に任意のPerl関数、つまりカスタム関数を追加することもできます。
以下は、ファイルを読み取り、eval
ディレクティブと組み合わせてPOST
ファイルを実装する関数を追加する例です。
sub read_file {
my $infile = shift;
open my $in, $infile
or die "cannot open $infile for reading: $!";
my $content = do { local $/; <$in> };
close $in;
$content;
}
our $CONTENT = read_file("t/test.jpg");
run_tests;
__DATA__
=== TEST 1: sanity
--- request eval
"POST /\n$::CONTENT"
シャッフル
上記以外に、test::nginx
にはあまり知られていない落とし穴があります。デフォルトでは、テストケースをランダムな順序で実行します。テストケースの順序と番号に従わないのです。
これは、より多くの問題をテストするために当初意図されていました。結局のところ、各テストケースが実行された後、NGINXプロセスが閉じられ、新しいNGINXプロセスが起動して実行されるため、結果は順序に関係ないはずです。
基盤レベルのプロジェクトでは、これは事実です。しかし、アプリケーションレベルのプロジェクトでは、データベースなどの永続的なストレージが外部に存在します。でたらめな実行は、誤った結果を招く可能性があります。毎回ランダムであるため、エラーが報告される場合とされない場合があり、エラーも毎回異なる可能性があります。これは明らかに開発者にとって混乱を招きます。私も何度もここでつまずきました。
したがって、私のアドバイスは次のとおりです。この機能をオフにしてください。以下の2行のコードでオフにできます。
no_shuffle();
run_tests;
特に、no_shuffle
はランダム化を無効にし、テストケースの順序に厳密に従ってテストを実行します。
reindex
OpenRestyのテストケースセットには、厳格なフォーマット要件があります。各テストケースは3つの改行で区切る必要があり、テストケースの番号付けは厳密に自己増加する必要があります。
幸いなことに、この面倒な作業を行う自動ツールreindex
があります。これはopenresty-devel-utilsプロジェクトに隠されています。これに関するドキュメントはないため、ごく少数の人しか知りません。
興味があれば、テストケースの番号付けを混乱させたり、改行の数を増減させたりして、このツールを使用して整理し、元に戻せるかどうか試してみてください。
まとめ
これでtest::nginx
の紹介は終わりです。もちろん、もっと多くの機能がありますが、核心的で最も重要なものだけを話しました。「魚を与えるのではなく、魚の釣り方を教えよ。」私はテストを学ぶための基本的な方法と注意点を教えました。その後、公式のテストケースセットを掘り下げて、より深く理解することができます。
最後に、以下の質問を考えてみてください。プロジェクト開発においてテストはありますか?そして、どのフレームワークを使用してテストしていますか?この記事を多くの人と共有して、交流し、学び合うことを歓迎します。