shellspec 0.12.0 リリースしました

前回書いたのは、0.7.0の時ですか・・・。リリースするたびにブログ書かなきゃなと思いつつ、コード書いていたのでずいぶんバージョンが上がりました。

まだメジャーバージョン0なのを良いことに、互換性がない変更もガシガシしています。といってももうそろそろ落ち着いたかなーって感じですが。

ということで 0.12.0 までの変更について駆け足で紹介したいと思います。

0.8.0での変更

一番の変更点は It の意味を Example のエイリアスに変更した所です。

やっつけで作っていたサンプルと、shellspec自身のテストを見直していたのですが、英語的に適切な文章になるようにと。そうするとやっぱり、 ItExample のエイリアスであったほうが良いなと思い直したので変更しました。これで自然な文章が作れるようになりました。

でもこの「自然な文章」というのはやっぱり英語なわけで、テストを書いてるのか英語の文章の書き方を練習しているのかよくわからない状態になりましたw 意味として通るような文章にするにはどうすればよいのか、そこは英語が母国語でない人の辛い所ですね。日本語で書くなら ExampleSpecify を使ったほうがいいのかもしれません。

そしてこのバージョンでの一番の目玉は、Data Helper と %text Directive です。両方共シェルスクリプトで使いづらいヒアドキュメントを改善するためにあります。

shellspec の specfile はシェルスクリプトと互換性がある文法ですがブロック構造をサポートしています。そのためインデントを多用することになります。こんな感じですね。

Describe 'sample' # Example group
  Describe 'bc command'
    add() { echo "$1 + $2" | bc; }

    It 'performs addition' # Example
      When call add 2 3 # Evaluation
      The output should eq 5  # Expectation
    End
  End
End

シェルスクリプトはヒアドキュメントをサポートしているのですが、ここにヒアドキュメントを入れるとこんな感じになってしまいます。

Describe 'sample' # Example group
  Describe 'bc command'
    add() { echo "$1 + $2" | bc; }
    func() {
        cat <<HERE
FOO
BAR
BAZ
HERE
    }

    It 'performs addition' # Example
      When call add 2 3 # Evaluation
      The output should eq 5  # Expectation
    End
  End
End

インデントが台無しです。一応シェルスクリプトのヒアドキュメントでもインデントはできるのですがタブ文字限定のうえ、ヒアドキュメントの終わりはインデントできません。

この問題を解決するのが、Data Helper と %text Directive です。具体的にはこのように書くことができます。

Describe 'sample'
  Describe 'sort command'
    Data
        #|FOO
        #|BAR
        #|BAZ
    End

    It 'sorts'
      When call sort
      The line 1 of output should eq BAR
      The line 2 of output should eq BAZ
      The line 3 of output should eq FOO
    End
  End

  Describe 'wc command'
    func() {
        %text
        #|FOO
        #|BAR
        #|BAZ
    }
    It 'counts lines'
      When call 'func | wc -l'
      The output should eq 3
    End
  End
End

コメントの形でテキストを書いておく事ができるのでインデントを崩さずに書くことができます。

0.9.0での変更

特に大きな変更はなし^^;

0.10.0での変更

このバージョンでは、新たに %puts Directive と %putsn Directive が加わりました。これは echo の代わりに使えるもので、シェル毎に細かい違いが有る echo とは違いエスケープ文字の解釈を行わずにそのまま出力します。(puts は出力の終わりに改行なし、putsn は改行ありです。それぞれ %-%= というエイリアスが使えます。)

そしてもう一つの目玉が、テストの並列実行です。面倒なので正直作りたくなかった(笑) どうして実装したかと言うと bats-core で実装されていたからです。気づいたときは、えーそれやってるのー?って感じで。まあ bats-core が実装してるなら、対抗して実装しましょうと。

bats-core の実装は GNU parallel を使っているようです。(以前これ使って数日かけて並列でデータの変換を行ったことがあるなぁと懐かしくなりつつ)ですが、shellspec は全てのシェルで動くのもアピールポイントの一つなので & を使用したバックグラウンドプロセスを使って実装しています。排他制御やCTRL-Cによる中断などへの対応などシェルスクリプトの機能だけを使って並列処理を行うのはなかなか大変でした。

並列でテストを実行すると(テスト内容やハードウェアに依存すると思いますが)Linux環境で2~3倍ぐらいまで速度をあげられるようです。実装していて気づいたのが並列じゃなくても意外とコアを複数利用していたことです。specfileの変換、テストの実行、レポーター、と複数のプロセスをパイプを使って処理させているので設計通りではあるのですがちゃんと調べていませんでした。そしてレポーターが意外とボトルネックになっていることにも気づきました。shellspec 自体のテストでは、テストの実行は終わっているのにレポーター単体でCPUを使ってる時間が半分ぐらいあったりと。そこを改善すればもう少し速くなるかもしれません。

0.11.0での変更

このバージョンでは、前から欲しかった行番号指定によるテストの実行とDSLの前に f をつけたもの(fDescribe, fContext, fExample, fSpecify, fIt)だけを実行する機能を実装しました。(仕様は rspec のパクリですw)これでようやくテストの実行が楽になりました。

shellspec を実行してエラーがあると以下のようなエラーが表示されるのですが、この一行をコピペするだけで該当のものだけテストできるのです。便利ですね!(もちろん rspec のパクリですw)

Failure examples:

shellspec spec/04.evaluation_spec.sh:27 # 3) evaluation sample call evaluation must be one call each example FAILED
shellspec spec/04.evaluation_spec.sh:37 # 4) evaluation sample call evaluation can not be called after expectation FAILED

rspec の仕様をパクりまくってる shellspec ですが少し異なる違う部分がありまして、rspec では実行するテストの中に f がついたものが一つでもあれば、それだけを実行するのに対して shellspec では --focus オプションが必要ということです。この理由は shellspec の設計がspecfileを1パスで処理するようになっているからです。つまりspecfileの実行前にどのようなテストがあるかを確認していません。specfileの変換を行うと同時に実行しています。(唯一の例外は TAP 出力を行うときで、TAPの仕様で最初にテスト総数を出力する必要があるため先にテストの数を数えています。この場合でもテストの実行は行っていません。)

0.12.0での変更

これは落ち葉拾い的なリリースですね。shellspec ではテストの順番はファイルが見つかった順に行うのですが、rspec にはテスト順のランダム化の機能があります。これを実装するためにはシェルスクリプトだけで乱数を計算する機能があるのですが少し面倒なのと、個人的にテスト順のランダム化をあまり重視してないので、代わりに(テストを実行せずに)全specfileの一覧と全exampleの一覧を出すオプションを追加しました。これを利用するとこんな感じでランダム順でテストできます。ただし --list-examples の方は example の数だけ specfile の変換とレポート処理を行うのでかなり遅くなりますが。

shellspec $(shellspec --list-specfiles | shuf)
shellspec $(shellspec --list-examples | shuf)

そしてもう一つの追加機能が --env-from によるスクリプトファイルを使った環境変数の設定機能です。これを使うと実行してるシェルが bash や zsh などの配列をサポートしているシェルの場合だけ、追加のテストを読み込む(ための環境変数を設定する)ことができます。似たようなことは DSL の Skip で行うことができるのですが、文法によっては、dash で読み込むだけでシンタックスエラーになってしまうものがあります。そのため拡張子を変更しておき、デフォルトでは _spec.sh のみ、配列をサポートしているシェルでは spec.sh と *spec.array.sh を読み込むように環境変数 SHELLSPEC_FILTER を設定しています。まだ bash や zsh 固有の機能に対応する機能がそんなにあるわけではないのですが、これで将来的に対応しやすくなりました。