weblog of key_amb

主にIT関連の技術メモ

"shove" というシェルスクリプト用のテストツールを作った

CONTENTS:

"shove" とは

こちらです。

github.com

動作イメージとしては README.md にも貼っているスクリーンショットがわかりやすいと思うので、こちらにも貼ります。

成功時:

f:id:key_amb:20160423110652p:plain

失敗時:

f:id:key_amb:20160423110705p:plain

上のようにテストスクリプトを引数に与えて shove コマンドを実行すると、テストを実行して結果を出力してくれます。

Perl Monger のみなさんは「proveっぽい」と思っていただけたでしょうか。
そうです。名前の "shove" は "shell" + "prove" を縮めたものです。

bash だけでなく、ksh, dash, zsh など POSIX を満たしているシェルに対応できるように作りました。
$SHELL 変数の値と異なるシェルでテストを実行したい場合は、 shove -s|--shell シェル という形で指定してください。

Motivation

一言で言って、きっかけは Yak Shaving です。

最初に「シェルスクリプトのテストを書きたい」と思ったのは、PATH 操作ツール sh-pathctl を作ったときで、2週間ほど前のことでした。

pathctl は元々 Bash で使うことしか考えてなかったこともあり*1、最初に見つけたのは Bats でした。作者の sstephensonrbenv の作者でもあり、rbenv も Bats でテストされているようです。

Bats のテストコードでは、@test "description" { ... } で括った ... 部分の Bash コードが1つのテストケースとなり、set -e で実行して 1行でも失敗したら FAIL というものです。

シンプルですが、まあこれで十分かな、という感じで、しばらくこれを使いました。

ところが、その後、思いつきでシェルスクリプトのパッケージ管理システムみたいなものを作りたくなり、実際に今 clenv というリポジトリで作っています。*2
…で、そのテストを Bash 以外でもやりたくなりました。

Bash 以外でも使えるテストツールを探してみたところ、思ったより色々あるようです。見つけたものを下に列挙します。

shUnit2 を少し試してみましたが、なんだか面倒であまり使い続ける気になりませんでした。
Sharness は README を見て、インタフェースにうーんとなってしまいました。

そんなに大したことをしたいわけでもないので、自作でも行けるだろう、と思って作り始め、まあそれなりに使えるものになったかな、というところです。

こんなツールにしたい

設計意図のようなものを下に並べます。

  • テストコードをシェルスクリプトの文法で書きたい
  • テストツールに余計なことをしてほしくない。書いた通りに動いてほしい
  • 関連するテストをグループ化したい

何やってるのかわからなくて学習コストの高いテストフレームワークは嫌だな、と思っています。*3

今のところ、中で少し面倒な変換をしているところもあるのですが、概ね上に挙げたことは実現できているかと思っています。

使い方について

README.md#Usage で一通り網羅できているかと思います。

1つ、具体例を書いてみましょう。
下のようなシェルスクリプトがあったとします。

# add.sh
add() {
  eval tmp=$"$1"
  eval $1=$(expr $tmp + $2)
  unset tmp
}

テストコードは、例えば下のように書けます。

# add.t
. add.sh

var=3
t_ok $var "var"

T_SUB "add A <num>" ((
  add var 3
  t_is $var 6 "3 + 3 = 6"
  add var 5
  t_is $var 11 "6 + 5 = 11"
))

t_is $var 3 "subtest doesn't affect main context"

テストを実行すると下のようになります。

% shove add.t -v
Run tests by /bin/zsh
-------------------------
add.t ...
ok 1 - var
  # add A <num>
  ok 1 - 3 + 3 = 6
  ok 2 - 6 + 5 = 11
  1..2
ok 2 - "add A <num>"
ok 3 - subtest doesn't affect main context
1..3
ok

Test Summary Report
-------------------
All tests successful.
Files=1, Tests=3, Successes=3, Failures=0
Result: PASS

中で何をやっているのか

shove foo.t bar.t を実行すると、下のようなことを実行します。

  • foo.t bar.t を元に、それぞれテストスクリプト$SHOVE_WORKDIR (デフォルトは $HOME/.shove) に生成します。
    • 先頭に以下の2行を足します。
      • . /path/to/shove/lib/t.shrc ... lib/t.shrc にテスト用の関数が定義されています。
      • t_init ... 初期化処理関数
    • 末尾に t_report $datfile という行を足します。$datfile にはテストの総数、成功数、失敗数が記録されます。
    • T_SUB "subtest name" (()) という行を発見すると、テストをグループ化するコードに変換します。
  • 生成したテストスクリプトを実行します。
  • それぞれの $datfile のデータを集計します。
  • 総テスト結果を表示します。

おわりに

自作のテストツール "shove" の紹介をしました。

機会がありましたら、どうぞご利用ください。

また、使ってみて何かトラブルがありましたら、GitHub Issuesなどでお知らせ下さい。

それでは、Have Fun!

*1:今は pathctl のテストも shove で行っています。

*2:clenv については、またその内ブログを書くと思います。

*3:RSpec は難しいですね。。頑張ってちょっとずつ覚えてます^^;