126
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Organization

Batsを使って手軽にCLIプログラムのテストをする

Bats

BatsはCLIで実行するUNIXプログラムのテストをするためのツールです。
Bash Automated Testing SystemでBatsとのこと。

Bats自体がbashで書かれていて、特にbashスクリプトのテストに最適なようですが、出力と終了ステータスをチェックするような単純な作りなので、CLIで動作するプログラムであれば何でもテストできるでしょう。

元々、ruby-buildのテストファイル眺めてたら拡張子が*.batsになってて、「なんだろこれ?」と思って見たら同じ作者のBatsというツールでした。
使ってみたら結構手軽で便利だったので紹介します。

簡単な例

以下の例を見れば大体どんな感じかわかると思います。
bc, dcの演算結果をチェックするためのテストですね。

#!/usr/bin/env bats

@test "addition using bc" {
  result="$(echo 2+2 | bc)"
  [ "$result" -eq 4 ]
}

@test "addition using dc" {
  result="$(echo 2 2+p | dc)"
  [ "$result" -eq 4 ]
}

インストールのしかた

MacだったらHomebrewでインストールできます。

$ brew install bats

ソースからインストールする場合はこちらの手順で。
/usr/local以下にBatsがインストールされます。

$ git clone https://github.com/sstephenson/bats.git
$ cd bats
$ ./install.sh /usr/local

Batsでなんかテストしてみる。

では、これから適当なコマンドを使ってテストしてみましょう。

テストするコマンドの仕様

例えばこういう仕様の check_if_file_exists.sh っていうシェルスクリプトのコマンドがあったとします。

  • ファイルの存在チェックを行うコマンド
  • 引数でファイル名を指定する
  • 引数が未指定や2個以上指定されているとステータス1でエラー終了
  • ファイルが存在したが、ディレクトリだった場合はステータス2でエラー終了
  • ファイルが存在しなかった場合はステータス3でエラー終了
  • ファイルが存在した場合はステータス0で正常終了

テストの内容

Batsのテスト記法にしたがって書くと、こんな感じのテストになります。

[test-check_if_file_exists.bats]

#!/usr/bin/env bats

setup() {
  mkdir test_dir
  touch test_file
}

teardown() {
  rmdir test_dir
  rm test_file
}

@test "When no argument provided, it should fail with exit code 1 and print error message" {
  run ./check_if_file_exists.sh
  [ "$status" -eq 1 ]
  [ "${lines[0]}" = "You should specify a file name to check as an argument." ]
}

@test "When more than 1 arguments provided, it should fail with exit code 1 and print error message" {
  run ./check_if_file_exists.sh
  [ "$status" -eq 1 ]
  [ "${lines[0]}" = "You should specify a file name to check as an argument." ]
}

@test "When the provided file is kind of a directory, it should fail with exit code 2 and print error message" {
  run ./check_if_file_exists.sh test_dir
  [ "$status" -eq 2 ]
  [ "${lines[1]}" = "but it seems a directory." ]
}

@test "When the provided filename does not exist, it should fail with exit code 3 and print error message" {
  run ./check_if_file_exists.sh nonexistance
  [ "$status" -eq 3 ]
  [ "${lines[0]}" = "nonexistance does not exist." ]
}

@test "When the provided filename exist, it should end normally with exit code 0 and print message" {
  run ./check_if_file_exists.sh test_file
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "test_file exists." ]
}

ポイント

setup と teardown

unittestではおなじみのこれらも使えます。

  • setup
    各テスト実行前に実行されるフック関数。
    テスト用の環境設定とかに使う
  • teardown
    各テスト実行後に実行されるフック関数。
    テストの後処理とかに使う

@test

@test に続けてテストの説明を書きます。
ブロック内にテストの本体を書きます。

run

run の引数に実行するコマンド、つまりテスト対象を指定します。
コマンドの実行後に、実行結果の出力内容と終了ステータスを $output, $status といった特別な変数に格納します。

$status

コマンドの終了ステータスを格納する変数です。

$output

コマンドの出力内容を格納する変数です。

$lines

出力内容の 行ごとに格納する 配列変数です。

実際にテストするコマンド

こんなシェルスクリプトだったとします。
あえてバグを仕込んでます。

[ check_if_file_exists.sh ]

#!/usr/bin/env bash

if [ $# -ne 1 ]; then
    echo "You should specify a file name to check as an argument." >&2
    exit 1
fi

FILE_NAME=$1

if [ -f $FILE_NAME ]; then
    echo "$FILE_NAME exists."
    exit 0
elif [ -d $FILE_NAME ]; then
    echo "$FILE_NAME exists," >&2
    echo "but it seems a directory." >&2
    exit 2
else
    echo "$FILE_NAME does not exist." >&2
    exit 4 # バグ。ここは本来 3 で終了すべき仕様
fi

テストの実行

では、テストを実行してみます。

スクリーンショット 2014-09-06 21.07.26.png

$ ./test-check_if_file_exists.bats
 ✓ When no argument provided, it should fail with exit code 1 and print error message
 ✓ When more than 1 arguments provided, it should fail with exit code 1 and print error message
 ✓ When the provided file is kind of a directory, it should fail with exit code 2 and print error message
 ✗ When the provided filename does not exist, it should fail with exit code 3 and print error message
   (in test file test-check_if_file_exists.bats, line 33)
     `[ "$status" -eq 3 ]' failed
 ✓ When the provided filename exist, it should end normally with exit code 0 and print message

5 tests, 1 failure

期待通りに、あえてバグを仕込んだテストのみ失敗して、他は成功しています。
bashで実装されているので特に何の準備もなく導入できて、手軽にCLIプログラムのテストをできるので結構便利に使えます。

上で書いた通り、bashスクリプトに限らず、どんなCLIプログラムでも、簡単なテストなら手軽にできます。

[check_if_file_exists.rb]

#!/usr/bin/env ruby

unless ARGV.length == 1
  STDERR.puts "You should specify a file name to check as an argument."
  exit 1
end

file_name = ARGV[0]

if File.exist?(file_name)
  if File.ftype(file_name) == 'file'
    puts "#{file_name} exists."
  elsif File.ftype(file_name) == 'directory'
    STDERR.puts "#{file_name} exists,\nbut it seems a directory."
    exit 2
  else
  end
else
  STDERR.puts "#{file_name} does not exist."
  exit 4
end
$ ./test-check_if_file_exists2.bats
 ✓ When no argument provided, it should fail with exit code 1 and print error message
 ✓ When more than 1 arguments provided, it should fail with exit code 1 and print error message
 ✓ When the provided file is kind of a directory, it should fail with exit code 2 and print error message
 ✗ When the provided filename does not exist, it should fail with exit code 3 and print error message
   (in test file test-check_if_file_exists2.bats, line 33)
     `[ "$status" -eq 3 ]' failed
 ✓ When the provided filename exist, it should end normally with exit code 0 and print message

5 tests, 1 failure

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
126
Help us understand the problem. What are the problem?