BATSでbashスクリプトのテストをする #bats #bash

概要

bashスクリプトのテストコードを書く方法がないか調べていたら、BATSというのが見つかって中々よかった。

github.com

下記の記事を鵜呑みにして試してみてたのだけれど、sstephensonが有名人であることが上司からの突っ込みで発覚した。
GitHubちゃんと見ると、rbenvとかbasecampの文字がありましたねw)

postd.cc

インストール

Macだとbrewで導入可能。

brew install bats

実行方法

テストはbatsという拡張子のファイルを作って、batsコマンドに渡すだけ。

bats test_sample.bats

batsファイル

下記のような感じで書く。

setupとteardownが用意されているのはうれしい。

#!/usr/bin/env bats

# ケース実行前の処理
# 処理が何もない場合エラーになるので、必要なときだけ実装する
setup() {
  mkdir test_dir
}

# ケース実行後の処理
# 処理が何もない場合エラーになるので、必要なときだけ実装する
teardown() {
  rmdir test_dir
}

@test "test sample" {
  # テスト対象処理はrunで実行する
  # runで実行することで、BATSの特殊変数等を利用できる
  run ./sample.sh

  # assertはテストコマンドで行う
  # status変数にはrunで実行したコマンドの終了ステータスが格納される
  [ "${status}" -eq 0 ]
  # output変数にはコマンドの出力内容が格納される
  [ "${output}" = "Success!" ]
  # lines変数には行毎に配列でコマンドの出力内容が格納される
  [ "${lines[0]}" = "Success!" ]
}

runでは知らせると、status/output/linesにそれぞれ値が格納される。

出力内容やステータスが代入されるので、その値をアサーションしていく。

外部スクリプトの読み込み

テストのヘルパー的なものや、テスト対象が関数に絞られる場合、loadを使って読み込みできる。

loadした後は関数だけrunすることもできるし、テストメソッド内で単純に実行もできる。

load core_function
load test_helper

拡張子がbashの場合、相対パスかつ拡張子なしで記載する。

拡張子が異なる場合はフルパスで記載する必要がある。

なお、loadはテストメソッド内外どちらでも利用可能。

関数と実処理を分離する

関数だけのテストをしたい場合、loadでfunctionを読み込むことになると思う。

しかしload は結局 source みたいなものなので、load時点でスクリプトが実行されてしまう。

もし既存のコードに部分的にテストを足したい場合、関数と実行処理を分ける方針にするといいかもしれない。

Mock

Mockが必要になるような複雑な処理までbashでがんばるなとは思うけど、現実世界はどうにも厳しいことがあると思う。

コマンドや関数をMock化したい場合は同名の関数を定義して上書きするとよい。

curl() {
  echo "curl command is called."
  return 0
}

上述の場合、curlコマンドを上書きしている。

通信はせず標準出力した上で常に0を返すようになっている。

関数定義はテストメソッド内外どちらでも可能だけれど、基本的にはテストメソッドに閉じる形にしておくのがよいと思う。

BATSの動き的にはテストメソッド毎に関数定義は読み直してくれるようなのであまりないと思うが、Mockを元に戻すには unset コマンドを使えばよい。

unset curl

CI

CircleCIでテストを走らせるようにしてみた。

2.0のクラシックイメージにはそもそもBATSが入っているようで、特にインストール等は必要なかった。

config.ymlは下記のような感じ。

version: 2
jobs:
  build:
    docker:
      - image: sukun1899/bats
    steps:
      - checkout
      - run: bats -t .

まとめ

  • bashでもテスト書ける
  • でもbashで無理すんな

参考

いろいろ試したのは下記に置いてある。

github.com