このエントリは, 「Perl Advent Calendar 2020」の9日目の記事です.
昨日のエントリは, id:xtetsuji さんの「xargs や find と合わせて使う・代わりに使う Perl」でした.
実は最近異動をしていた id:papix です. 異動後もPerlをモリモリ書いている日々ですが, 移動先のチームのプロダクトで同僚の id:mizdra が導入していた Test::Snapshot が便利だったので紹介します.
Test::Snapshot
Test::Snapshotは, その名の通り「スナップショットテスト」を提供するモジュールです. スナップショットテストとは, 予め「スナップショット」と呼ばれる期待値を生成しておき, テストを実行する際には実行結果とスナップショットを比較してテストをする手法です.
使い方
まず初めに, Test::Snapshotを利用したテストの実例を見てみましょう:
use Test2::V0; use Test::Snapshot; sub method { return { a => 1, b => 2, c => [3, 4, 5], }; }; is_deeply_snapshot method(), 'hashref'; done_testing;
これは, method()
の返り値をスナップショットテストで検証するコードになります. なおここでは, 単純にするためにテストスクリプトに直接テスト対象となる実装(method
)を書いています.
このコードを, snapshot.t
という名前で保存し, prove
で実行してみます.
$ prove -v snapshot.t snapshot.t .. # Seeded srand with seed '20201209' from local date. # No snapshot filename '/path/to/dir/snapshots/snapshot_t/hashref' found not ok 1 - hashref # Failed test 'hashref' # at snapshot.t line 12. # @@ -1 +1,9 @@ # -undef # +{ # + 'a' => 1, # + 'b' => 2, # + 'c' => [ # + 3, # + 4, # + 5 # + ] # +} 1..1 # Looks like you failed 1 test of 1. Dubious, test returned 1 (wstat 256, 0x100) Failed 1/1 subtests Test Summary Report ------------------- snapshot.t (Wstat: 256 Tests: 1 Failed: 1) Failed test: 1 Non-zero exit status: 1 Files=1, Tests=1, 0 wallclock secs ( 0.02 usr 0.00 sys + 0.07 cusr 0.02 csys = 0.11 CPU) Result: FAIL
まだスナップショットを生成していないため, 当然ですがテストは失敗しますね.
# -undef # +{ # + 'a' => 1, # + 'b' => 2, # + 'c' => [ # + 3, # + 4, # + 5 # + ] # +}
Test::Snapshotの便利なところとして, このようにスナップショットと実行結果の差をいい感じにダンプしてくれる機能があります.
ここでは, スナップショットがまだ存在しないため undef
となっていることがわかります.
スナップショットの生成
さて, 続いてスナップショットを生成してみましょう. スナップショットを生成するには, TEST_SNAPSHOT_UPDATE
という環境変数に真値をセットした上でテストを実行すればよいです.
$ TEST_SNAPSHOT_UPDATE=1 prove -v snapshot.t snapshot.t .. # Seeded srand with seed '20201209' from local date. # No snapshot filename '/path/to/dir/snapshots/snapshot_t/hashref' found not ok 1 - hashref # Failed test 'hashref' # at snapshot.t line 12. # @@ -1 +1,12 @@ # -undef # +{ # + 'a' => 1, # + 'b' => 2, # + 'c' => [ # + 3, # + 4, # + 5, # + { # + 'd' => 10 # + } # + ] # +} 1..1 # Looks like you failed 1 test of 1. Dubious, test returned 1 (wstat 256, 0x100) Failed 1/1 subtests Test Summary Report ------------------- snapshot.t (Wstat: 256 Tests: 1 Failed: 1) Failed test: 1 Non-zero exit status: 1 Files=1, Tests=1, 0 wallclock secs ( 0.02 usr 0.01 sys + 0.08 cusr 0.02 csys = 0.13 CPU) Result: FAIL
実行すると, テストは失敗しますが, snapshot.t
があるディレクトリに snapshots
というディレクトリが設置されます.
$ tree . ├── snapshot.t └── snapshots └── snapshot_t └── hashref 2 directories, 2 files
tree
するとこんな感じ. shapshots
ディレクトリの中に, テストファイルに基づいたディレクトリ(テストがsnapshot.t
なので, snapshot_t
)が設置され, 更にその中にスナップショットの実態であるhashref
というファイルが設置されます. ちなみにこのファイル名は, is_deeply_snapshot
の第2引数(description)から生成されます.
注意点として, ファイル名はdescriptionに含まれる文字から, a
からz
までのアルファベット(大文字小文字問わず)と0
から9
までの数字, そして-
以外を全てまとめて_
に置換したものになります.
そのため, 次のようなテストを書いてしまうと, method()
とother_method()
のスナップショットがどちらも_
というファイルに書き込まれてしまいます(!?).
is_deeply_snapshot method(), 'ひとつめのテストです'; is_deeply_snapshot other_method(), 'ふたつめのテストです';
スナップショット生成後のテスト
さて, スナップショットが生成できたので, 改めてテストを実行してみましょう.
$ prove -v snapshot.t snapshot.t .. # Seeded srand with seed '20201209' from local date. ok 1 - hashref 1..1 ok All tests successful. Files=1, Tests=1, 0 wallclock secs ( 0.02 usr 0.00 sys + 0.08 cusr 0.02 csys = 0.12 CPU) Result: PASS
method()
の実行結果とスナップショットは等しいので, 当然テストは成功します.
ここで改めて, method
の実装を変更してテストを実行してみましょう.
use Test2::V0; use Test::Snapshot; sub method { return { a => 1, b => 2, c => [3, 4, 5, { d => 10 }], }; }; is_deeply_snapshot method(), 'hashref'; done_testing;
意図したようにテストは失敗し, その差分を表示してくれます.
$ prove -v snapshot.t snapshot.t .. # Seeded srand with seed '20201209' from local date. not ok 1 - hashref # Failed test 'hashref' # at snapshot.t line 12. # @@ -4,6 +4,9 @@ # 'c' => [ # 3, # 4, # - 5 # + 5, # + { # + 'd' => 10 # + } # ] # } 1..1 # Looks like you failed 1 test of 1. Dubious, test returned 1 (wstat 256, 0x100) Failed 1/1 subtests Test Summary Report ------------------- snapshot.t (Wstat: 256 Tests: 1 Failed: 1) Failed test: 1 Non-zero exit status: 1 Files=1, Tests=1, 1 wallclock secs ( 0.02 usr 0.00 sys + 0.08 cusr 0.02 csys = 0.12 CPU) Result: FAIL
まとめ
さくっとではありますが, Perlでスナップショットテストを実現する, Test::Snapshotを紹介しました. スナップショットを配置するディレクトリやファイル名に若干クセがある(変更はできなさそうでした)ものの, シンプルで便利なモジュールでした. 依存モジュールがかなり少なく, 基本的な(よく使う)モジュールが多いところも嬉しいです.
明日の担当は, @Taroupho さんです. よろしくおねがいします.