- 追加された行はこの色です。
- 削除された行はこの色です。
- bats へ行く。
#author("2020-02-16T15:41:19+09:00","default:haikikyou","haikikyou") #author("2020-02-16T15:41:31+09:00","default:haikikyou","haikikyou") [[moritetuのIT関連技術メモ]] #contents * bats [#mee50b91] Bashで書かれたテストツールで、コマンドラインで実行するプログラムの振る舞いをチェックするために有用である。~ コマンドラインから実行できるプログラムの動作確認として幅広く使うことができる。~ 何といっても、batsは非常に小さなプログラムで軽量であるのが特徴である。~ Bashで動作するので、大抵のUnix/Linux環境で何も特別なライブラリを必要とせず導入できる。 * インストール [#g8a0fc67] 非常に簡単で、&code(){install.sh};を実行するのみである。~ &code(){install.sh};の引数で指定された&code(){PREFIX};にbatsがインストールされる。~ batsプログラムに加えmanもインストールされるので、&code(){man bats};でマニュアルを確認できる。 #geshi(bash){{{ $ git clone https://github.com/sstephenson/bats.git $ bash ./bats/install.sh /usr/local Installed Bats to /usr/local/bin/bats $ bats Bats 0.4.0 Usage: bats [-c] [-p | -t] <test> [<test> ...] $ man bats }}} &label(warn){参考}; ''man bats'' - https://www.systutorials.com/docs/linux/man/1-bats/ - https://man.kusakata.com/man/bats.1.html - https://man.kusakata.com/man/bats.7.html * テスト [#t9a147b0] bats自体のテストがbatsで書かれているので、お手本はbatsのテストプログラムを見るのがよい。 https://github.com/sstephenson/bats/tree/master/test ** テストプログラム [#t1521afa] -テストはBashで記述する。ただ、batsはテスト実行前にプリプロセスが入る。プリプロセスの中で、bats独自形式のシンタックスをBashで実行可能な形式に変換するとともに、テスト実行のための準備を行なっている。 - テストファイルの拡張子は、&code(){.bats};である。&code(){.bats};でなくとも単一ファイルであれば実行できるが、test suite(複数のファイルをまとめて実行)では、&code(){.bats};であることを期待している。~ よって、ファイルの拡張子は&code(){.bats};で作成しておけばよい。 - テストプログラムはBashスクリプトであり、終了ステータスがエラー結果を生む場合、テストは失敗となる。 - テストは、ファイル内の上から定義されている順に実行される。 &label(sample){例1}; ''sample.bats'' #geshi(bash){{{ #!/usr/bin/env bats # This is a sample test program with bats. @test "Here is the title of the test" { # This is a test program run echo "hoge" [ $status -eq 0 ] } }}} 実行してみよう。 #geshi(bash){{{ $ bats sample.bats ✓ Here is the title of the test 1 test, 0 failures }}} ** テストスイートの実行 [#d62bdc80] batsコマンドにディレクトリを指定すると、ディレクトリ下の.batsファイルを順に実行してくれる。~ テストは、glob(&code(){*.bats};)にマッチするファイルが対象である。~ また、検索対象は指定されたディレクトリ階層下であり、2階層以上にのファイルは対象ではない。 #geshi(bash){{{ $ tree tests tests ├── bar.bats └── foo.bats 0 directories, 2 files $ bats tests ✓ test2 ✓ test1 2 tests, 0 failures }}} * 特徴・機能 [#x2d58e4d] ** setup、teardown [#c14645e8] 各テスト実行前に&code(){setup};、テスト終了時に&code(){teardown};を実行できる。~ プリプロセスで変換されたテスト関数を見ると、テストプログラムの前にsetup関数が呼ばれることがわかる(&code(){bats_test_begin};)。~ &code(){teardown};は、exit trapで実行される。 &label(sample){例}; ''setup, teardownの例'' #geshi(bash){{{ #!/usr/bin/env bats setup() { TEST_DATA="test" } teardown() { : nothing } @test "test2" { [ "$TEST_DATA" = "test" ] } @test "test3" { [ "$TEST_DATA" = "test" ] } }}} 実行結果は以下のとおり。 #geshi(bash){{{ $ bats bar.bats ✓ test2 ✓ test3 2 tests, 0 failures }}} ** ヘルパー [#z2a5bcb8] 必要な機能やデータは、&code(){load};コマンドでインクルードできる。~ これは、&code(){source};コマンドのラッパーである。~ ファイルパスは、絶対パスまたはテスト対象ファイルからの相対パスである。 インクルードするファイルは、拡張子が&code(){.bash};であることを期待している。 #geshi(bash){{{ $ cat my.bash ENVIRONMENT=dev $ cat helper.bats load my @test "Load test" { [ "$ENVIRONMENT" = "dev" ] } $ bats helper.bats ✓ Load test 1 test, 0 failures }}} ** runコマンドの結果 [#p4ad51c3] &code(){run};コマンドで実行したコマンドの内容は、以下の変数で参照できる。 |~変数名|~説明|h |output|標準出力、標準エラー出力(標準出力にリダイレクトされる)| |lines|outputの結果を改行(\n)区切りで分割した複数行を含む配列| |status|終了ステータス| ** テスト内の出力確認 [#oa8e8842] テストの中で、echoして変数の中身を出力したりして動作確認したいことがある。~ しかし、関数実行の出力(stdout、stderr)は一時ファイル(通常は、&code(){/tmp/bats.$$.out};)にリダイレクトされているため確認することはできない。これは、batsが一時出力をキャプチャし、formatするためである。この一時ファイルは、テスト終了時にrmされるため確認できない。 #geshi(bash){{{ # sample.bats @test "Here is the title of the test" { # echo "my message"は一度キャプチャされる echo "my message" run echo "hoge" [ $status -eq 0 ] } }}} ~ 方法としては以下がある。 &label(study){実験}; ''エラー時に確認する'' テストに失敗すれば、エラー時のダンプで出力される。 #geshi(bash){{{ @test "Here is the title of the test" { echo "my message" run echo "hoge" [ $status -eq 1 ] } }}} 実行すると以下のようになる。 #geshi(bash){{{ $ bats sample.bats ✗ Here is the title of the test (in test file sample.bats, line 8) `[ $status -eq 1 ]' failed my message 1 test, 1 failure }}} ~ &label(study){実験}; ''tapフォーマットでfd=3を使う'' 標準出力とエラー出力は、ファイルにリダイレクトされるが、fd=3は標準出力として使える。~ #geshi(bash){{{ @test "Here is the title of the test" { echo "my message" >&3 run echo "hoge" [ $status -eq 0 ] } }}} 実行してみると以下のようになる。~ tapの場合は、catにパススルーされるのでそのままメッセージを確認できる。 #geshi(bash){{{ $ bats -t sample3.bats 1..1 my message ok 1 Here is the title of the test }}} ~ &label(study){実験}; ''# とfd=3を使う'' formatterを敢えて交わすようなやり方。~ しかし、これはfailed用のフォーマット(&code(){#<space>};)となり良くない。 #geshi(bash){{{ @test "Here is the title of the test" { echo "# my message" >&3 run echo "hoge" [ $status -eq 0 ] } }}} 実行してみると以下のようになる。~ エラー出力用の&code(){# };を使用したため、本来のokのテストのメッセージを&color(red){赤字};で一部上書きして改行してしまった。 #geshi(bash){{{ $ bats sample4.bats my message title of the test 1/1 ✓ Here is the title of the test 1 test, 0 failures }}} ~ &label(study){実験}; ''log用のファイルに書く'' #geshi(bash){{{ $ cat sample.bats @test "Here is the title of the test" { echo "my message" >> mylog run echo "hoge" [ $status -eq 0 ] } }}} これは問題ないようだ。 ~ ~ &label(conclusion){結論}; テストOKの場合にも、どうしても出力を確認したい場合は、独自のログに書き出すのが良い(と思う)。~ 通常は、エラー時に見える出力で十分だろう。 ** その他 [#e41af78c] - テスト時のカレントディレクトリは、テスト実行した&code(){$PWD};である。~ ファイルを作成したり、削除したりする操作を含むテストの場合は、実行コンテキストを特に注意して意識して行なう。 - &code(){@test}; で同じ名前を使用してはならない。それより以前の定義済みのテスト関数が上書きされてしまうためである。~ この場合は、後続の上書きしたテスト関数が繰り返し実行される。 * プリプロセス [#r3a65fb9] batsは、テスト実行前に一度変換プロセスを挟むと前章で説明した。~ 具体的にどのようなファイルが作成されるか、参考までに以下で見てみる。 プリプロセスを実行している&code(){bats-preprocess};を実行してみよう。~ 先ほどの例1のsample.batsをプリプロセスにかけると以下のように変換されていることが分かる。~ 現在の実行コンテキストがどうなっているのか、以下の結果から判断することができる。~ 例えば、&code(){@test};の内部やファイルの内部で定義した変数の可視範囲、影響範囲は?といった内容である。 &label(sample){例}; ''sample.batsのプリプロセスの結果'' #geshi(bash){{{ $ cat sample.bats | bash /usr/local/libexec/bats-preprocess #!/usr/bin/env bats # This is a sample test program with bats. test_Here_is_the_title_of_the_test() { bats_test_begin "Here is the title of the test" 5; # This is a test program run echo "hoge" [ $status -eq 0 ] } bats_test_function test_Here_is_the_title_of_the_test }}} &code(){bats_test_function};は、引数で指定される関数をテスト関数として登録する。~ また、このファイルは別プロセス(サブシェル)でテスト関数実行毎に&code(){source};コマンドでインクルードされる。~ よって、同一ファイル中の複数のテストに渡って引き継ぎしたい変数定義は、テストファイルの&code(){@test { }}; の外のコンテキストで定義しておけばよい。~ &code(){@test {}}; の中で実行される内容はサブシェルで実行されるため、あるテスト関数内で定義した変数は、他のテスト関数に影響しない。 &label(sample){例2}; ''sample2.bats'' #geshi(bash){{{ #!/usr/bin/env bats # This is a sample test program with bats. GLOBAL="hoge" @test "Test1" { [ "$GLOBAL" = "hoge" ] export TEST1_ENV="TEST1" } @test "Test2" { [ "$TEST1_ENV" = "TEST1" ] } }}} 実行した結果は、以下のとおりである。 #geshi(bash){{{ $ bats sample2.bats ✓ Test1 ✗ Test2 (in test file sample2.bats, line 14) `[ "$TEST1_ENV" = "TEST1" ]' failed 2 tests, 1 failure }}} &label(memo){メモ}; ''ソースファイルはN+1回実行される?'' sample2.batsのグローバルコンテキスト(ここでは、@testブロックの外のこととする)に&code(){echo "included"};を仕掛けて実行した場合、以下のようになる。~ &code(){included};という文字が3回出力されている。テスト数+1回評価されていることが分かる。 #geshi(bash){{{ included 1..2 included begin 1 Test1 ok 1 Test1 included begin 2 Test2 not ok 2 Test2 # (in test file sample2.bats, line 14) # `[ "$TEST1_ENV" = "TEST1" ]' failed }}} &label(info){補足}; テストの流れについては、[[テスト実行の流れ概要>#kdb6c95f]]を参照のこと。 テストの流れについては、[[テスト実行の流れ概要>#runtest]]を参照のこと。 * テスト実行の流れ概要 [#kdb6c95f] #aname(runtest) ''単体ファイルの場合'' + ソースファイルがコンパイルされる。 + &code(){source};でコンパイル済みのソースがインクルードされる。~ + テスト件数を出力 + テストを実行する ++ サブプロセスでテスト関数実行 ++ ソースインクルード ++ テスト関数の実行~ --- stdout、stderrはリダイレクト、テスト終了時に削除される --- テスト時の出力は、エラー時には&code(){#<space>};の接頭辞付きでformatterに送られる。 ~ formatter側では、テスト時の出力をエラーメッセージの一部に表示 ++ すべてのテストが実行されるまで上記を繰り返す テスト関数の出力は、パイプ経由でformatterに送られレポートされる。 ~ &label(info){補足}; ''複数ファイルの場合'' すべてのテストファイルの件数のカウントのため、テスト実行前にテストファイルのコンパイルとインクルードが行われる。~ 単体ファイルのテストと異なり、上記分のコンパイル+インクルードが発生することに留意したい。 ~ テストは、再帰で上手く実行されている。 * 関連 [#s41a2e35] - batsライクなBashベースのテストツール -- https://github.com/moritetu/baut * 参考リンク [#de538270] - [[bats>https://github.com/sstephenson/bats]]- &size(11){&color(gray){on https://github.com/sstephenson/bats};};