bats

更新日: 2020-05-10 (日) 00:08:46 (399d)

moritetuのIT関連技術メモ

bats

Bashで書かれたテストツールで、コマンドラインで実行するプログラムの振る舞いをチェックするために有用である。
コマンドラインから実行できるプログラムの動作確認として幅広く使うことができる。

何といっても、batsは非常に小さなプログラムで軽量であるのが特徴である。
Bashで動作するので、大抵のUnix/Linux環境で何も特別なライブラリを必要とせず導入できる。

インストール

非常に簡単で、install.shを実行するのみである。
install.shの引数で指定されたPREFIXにbatsがインストールされる。
batsプログラムに加えmanもインストールされるので、man batsでマニュアルを確認できる。

$ 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

参考 man bats

テスト

bats自体のテストがbatsで書かれているので、お手本はbatsのテストプログラムを見るのがよい。

https://github.com/sstephenson/bats/tree/master/test

テストプログラム

  • テストはBashで記述する。ただ、batsはテスト実行前にプリプロセスが入る。プリプロセスの中で、bats独自形式のシンタックスをBashで実行可能な形式に変換するとともに、テスト実行のための準備を行なっている。
  • テストファイルの拡張子は、.batsである。.batsでなくとも単一ファイルであれば実行できるが、test suite(複数のファイルをまとめて実行)では、.batsであることを期待している。
    よって、ファイルの拡張子は.batsで作成しておけばよい。
  • テストプログラムはBashスクリプトであり、終了ステータスがエラー結果を生む場合、テストは失敗となる。
  • テストは、ファイル内の上から定義されている順に実行される。

例1 sample.bats

#!/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 ]
}

実行してみよう。

$ bats sample.bats
 ✓ Here is the title of the test

1 test, 0 failures

テストスイートの実行

batsコマンドにディレクトリを指定すると、ディレクトリ下の.batsファイルを順に実行してくれる。
テストは、glob(*.bats)にマッチするファイルが対象である。
また、検索対象は指定されたディレクトリ階層下であり、2階層以上にのファイルは対象ではない。

$ tree tests
tests
├── bar.bats
└── foo.bats

0 directories, 2 files
$ bats tests
 ✓ test2
 ✓ test1

2 tests, 0 failures

特徴・機能

setup、teardown

各テスト実行前にsetup、テスト終了時にteardownを実行できる。
プリプロセスで変換されたテスト関数を見ると、テストプログラムの前にsetup関数が呼ばれることがわかる(bats_test_begin)。
teardownは、exit trapで実行される。

setup, teardownの例

#!/usr/bin/env bats

setup() {
  TEST_DATA="test"
}

teardown() {
  : nothing
}

@test "test2" {
  [ "$TEST_DATA" = "test" ]
}

@test "test3" {
  [ "$TEST_DATA" = "test" ]
}

実行結果は以下のとおり。

$ bats bar.bats
 ✓ test2
 ✓ test3

2 tests, 0 failures

ヘルパー

必要な機能やデータは、loadコマンドでインクルードできる。
これは、sourceコマンドのラッパーである。
ファイルパスは、絶対パスまたはテスト対象ファイルからの相対パスである。

インクルードするファイルは、拡張子が.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コマンドの結果

runコマンドで実行したコマンドの内容は、以下の変数で参照できる。

変数名説明
output標準出力、標準エラー出力(標準出力にリダイレクトされる)
linesoutputの結果を改行(\n)区切りで分割した複数行を含む配列
status終了ステータス

テスト内の出力確認

テストの中で、echoして変数の中身を出力したりして動作確認したいことがある。
しかし、関数実行の出力(stdout、stderr)は一時ファイル(通常は、/tmp/bats.$$.out)にリダイレクトされているため確認することはできない。これは、batsが一時出力をキャプチャし、formatするためである。この一時ファイルは、テスト終了時にrmされるため確認できない。

# sample.bats
@test "Here is the title of the test" {
    # echo "my message"は一度キャプチャされる
    echo "my message"
    run echo "hoge"
    [ $status -eq 0 ]
}


方法としては以下がある。

実験 エラー時に確認する

テストに失敗すれば、エラー時のダンプで出力される。

@test "Here is the title of the test" {
    echo "my message"
    run echo "hoge"
    [ $status -eq 1 ]
}

実行すると以下のようになる。

$ 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


実験 tapフォーマットでfd=3を使う

標準出力とエラー出力は、ファイルにリダイレクトされるが、fd=3は標準出力として使える。

@test "Here is the title of the test" {
    echo "my message" >&3
    run echo "hoge"
    [ $status -eq 0 ]
}

実行してみると以下のようになる。
tapの場合は、catにパススルーされるのでそのままメッセージを確認できる。

$ bats -t sample3.bats
1..1
my message
ok 1 Here is the title of the test


実験 # とfd=3を使う

formatterを敢えて交わすようなやり方。
しかし、これはfailed用のフォーマット(#<space>)となり良くない。

@test "Here is the title of the test" {
    echo "# my message" >&3
    run echo "hoge"
    [ $status -eq 0 ]
}

実行してみると以下のようになる。
エラー出力用の# を使用したため、本来のokのテストのメッセージを赤字で一部上書きして改行してしまった。

$ bats sample4.bats
    my message title of the test                                                1/1
 ✓ Here is the title of the test

1 test, 0 failures


実験 log用のファイルに書く

$ cat sample.bats
@test "Here is the title of the test" {
    echo "my message" >> mylog
    run echo "hoge"
    [ $status -eq 0 ]
}

これは問題ないようだ。



結論

テストOKの場合にも、どうしても出力を確認したい場合は、独自のログに書き出すのが良い(と思う)。
通常は、エラー時に見える出力で十分だろう。

その他

  • テスト時のカレントディレクトリは、テスト実行した$PWDである。
    ファイルを作成したり、削除したりする操作を含むテストの場合は、実行コンテキストを特に注意して意識して行なう。
  • @test で同じ名前を使用してはならない。それより以前の定義済みのテスト関数が上書きされてしまうためである。
    この場合は、後続の上書きしたテスト関数が繰り返し実行される。

プリプロセス

batsは、テスト実行前に一度変換プロセスを挟むと前章で説明した。
具体的にどのようなファイルが作成されるか、参考までに以下で見てみる。

プリプロセスを実行しているbats-preprocessを実行してみよう。
先ほどの例1のsample.batsをプリプロセスにかけると以下のように変換されていることが分かる。
現在の実行コンテキストがどうなっているのか、以下の結果から判断することができる。
例えば、@testの内部やファイルの内部で定義した変数の可視範囲、影響範囲は?といった内容である。

sample.batsのプリプロセスの結果

$ 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

bats_test_functionは、引数で指定される関数をテスト関数として登録する。
また、このファイルは別プロセス(サブシェル)でテスト関数実行毎にsourceコマンドでインクルードされる。
よって、同一ファイル中の複数のテストに渡って引き継ぎしたい変数定義は、テストファイルの@test { } の外のコンテキストで定義しておけばよい。
@test {} の中で実行される内容はサブシェルで実行されるため、あるテスト関数内で定義した変数は、他のテスト関数に影響しない。

例2 sample2.bats

#!/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" ]
}

実行した結果は、以下のとおりである。

$ bats sample2.bats
 ✓ Test1
 ✗ Test2
   (in test file sample2.bats, line 14)
     `[ "$TEST1_ENV" = "TEST1" ]' failed

2 tests, 1 failure

メモ ソースファイルはN+1回実行される?

sample2.batsのグローバルコンテキスト(ここでは、@testブロックの外のこととする)にecho "included"を仕掛けて実行した場合、以下のようになる。
includedという文字が3回出力されている。テスト数+1回評価されていることが分かる。

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

補足 テストの流れについては、テスト実行の流れ概要を参照のこと。

テスト実行の流れ概要

単体ファイルの場合

  1. ソースファイルがコンパイルされる。
  2. sourceでコンパイル済みのソースがインクルードされる。
  3. テスト件数を出力
  4. テストを実行する
    1. サブプロセスでテスト関数実行
    2. ソースインクルード
    3. テスト関数の実行
      • stdout、stderrはリダイレクト、テスト終了時に削除される
      • テスト時の出力は、エラー時には#<space>の接頭辞付きでformatterに送られる。
        formatter側では、テスト時の出力をエラーメッセージの一部に表示
    4. すべてのテストが実行されるまで上記を繰り返す

テスト関数の出力は、パイプ経由でformatterに送られレポートされる。

補足 複数ファイルの場合

すべてのテストファイルの件数のカウントのため、テスト実行前にテストファイルのコンパイルとインクルードが行われる。
単体ファイルのテストと異なり、上記分のコンパイル+インクルードが発生することに留意したい。


テストは、再帰で上手く実行されている。

関連

参考リンク


トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
TOP | 閉じる | ダブルクリックで閉じる