Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

シェルスクリプトのテストを書こう!

駅メモチームでエンジニアをしている id:Eadaeda です。シバンは #!/usr/bin/envを使う派です。

皆さんはシェルスクリプト書いてますか? 環境構築、開発、テスト、ビルド、デプロイなどなど、一連の作業を自動化するための手段として時々出番があるんじゃないでしょうか。

ところでそのシェルスクリプト、テスト書いてますか?

シェルスクリプトのテスト

「シェルスクリプトのテスト〜?」って感じですよね。殆どの場合、一度書いてしまえばあんまり壊れることはないし別に…って感じですよね。わかります。実際開発環境のためにdocker compose upするだけのスクリプトなら雑でもいいですよね。

でも、重要な役割をもつスクリプトならどうでしょう。例えばアプリケーションのエントリーポイントや、リリースビルド・デプロイのためのスクリプトなどが思いつきます。
こういうのはテストである程度保証されていれば安心じゃないですか?どうですか?書きたくなってきましたか?とりあえず一回書いてみませんか?

Bats/ShellSpecでシェルスクリプトのテストを書いてみよう

シェルスクリプトにもテストフレームワークがあります。たとえばBatsShellSpecなどです。

今回は上記2つについて少しだけ紹介しようと思います。テスト対象のスクリプトは以下のものとします。

#!/usr/bin/env zsh

if [[ "${1}" == "en" ]]; then 
  echo "Hello World"
elif [[ "${1}" == "" ]]; then
  echo "こんにちは 世界"
fi

第一引数にenを渡せば Hello Worldを、何も渡さなければ こんにちは 世界と出力するだけのスクリプトです。これを bin/hello-world.shとして保存しておきます。

Bats

Batsは10年ぐらい前からあるそこそこ定番のテストフレームワークです。簡素に書けるのがいいところかなと思っています。

test/hello-world.batsとして以下の内容を保存します。

#!/usr/bin/env bats

# 出力を検査するシンプルなテスト。シェルの構文っぽく書く
@test "引数がないとき、こんにちは 世界が返されるべき" {
  result="$(./bin/hello-world.sh)"
  [ "$result" = "こんにちは 世界" ]
}

@test "引数がないとき、こんにちは 世界が返されるべき - 2" {
  # run を使うと $output に出力が格納される
  run ./bin/hello-world.sh
  [ "$output" = "こんにちは 世界" ]
}

# bats-core/bats-supportとbats-core/bats-assertを ./test/helpers 以下にcloneして読み込めば
# 様々なヘルパーが使えるようになって便利
load 'helpers/bats-support/load'
load 'helpers/bats-assert/load'

@test "引数がenのとき、Hello Worldが返されるべき" {
  run ./bin/hello-world.sh en
  # runの出力が一致しているかを見るヘルパー
  assert_output 'Hello World'
}

実行は batsコマンドに渡してやればよいです

$ bats ./test/hello-world.bats
hello-world.bats
 ✓ 引数がないとき、こんにちは 世界が返されるべき
 ✓ 引数がないとき、こんにちは 世界が返されるべき - 2
 ✓ 引数がenのとき、Hello Worldが返されるべき

3 tests, 0 failures

setup/teardownも書くことができます

#!/usr/bin/env bats

setup() {
  # ヘルパーのロードをsetupでやっちゃう
  load 'helpers/bats-support/load'
  load 'helpers/bats-assert/load'
}

@test "引数がenのとき、Hello Worldが返されるべき" {
  run ./bin/hello-world.sh en
  # setupでロードしてるのでヘルパーが使えちゃう
  assert_output 'Hello World'
}

ShellSpec

ShellSpecはBDDな単体テストフレームワークです。RSpecとかJestみたいな書き味でテストを書いていくことができ、機能も豊富です。

まずはプロジェクトのセットアップです。最低限.shellspecファイルが必要となりますが、shellspec --initで作成できるので、これを使うのが楽です。

$ shellspec --init
   create   /path/to/pwd/.shellspec
   create   /path/to/pwd/spec/spec_helper.sh

あとは spec/以下にテストを書いていきます。今回、READMEのTypical directory structureにならってファイル名はspec/bin/hello-world_spec.shとしました。内容は以下の通りです。なんだかプログラムっていうか普通の文章みたいになりますね。

Describe "bin/hello-world.shについて"
  Context "引数がないとき"
    It "こんにちは 世界が出力されるべき"
      When call ./bin/hello-world.sh
      The output should eq 'こんにちは 世界'
    End
  End

  Context "引数がenのとき"
    It "Hello Worldが出力されるべき"
      When call ./bin/hello-world.sh en
      The output should eq 'Hello World'
    End
  End
End

テストの実行は shellspec --initしたディレクトリでshellspecを実行するだけです

# --shell指定なしだと /bin/sh になる
$ shellspec --shell zsh
Running: /bin/zsh [zsh 5.8.1]
..

Finished in 0.27 seconds (user 0.04 seconds, sys 0.04 seconds)
2 examples, 0 failures

まとめ

今回はシェルスクリプトのテストフレームワークであるBatsとShellSpecの触りだけご紹介しました。どちらを使うかはREADMEを読んでみて決めてみてくださいね。

以上です。