shellspec の JUnit XML 出力対応について

shellspec を JUnit XML 出力に対応させようかなーって考えてます。

が、しかし、JUnit XML の公式な仕様が見つからない。というかそういうのはなさそう・・・

dev.classmethod.jp

いろいろ探しても情報が古そうだったりして、JUnit5で入れ子が可能になったはずだけど、XMLに変更はないのだろうか?以下はいろいろ探して見つけたリンク

github.com

<testsuites>
  <testsuite errors="0" failures="0" hostname="archbuild" id="0"
      name="StudioAllTests" package="test.infor.clearux.studio.integration"
      tests="456" time="107.824" timestamp="2008-01-23T10:49:26">
    <testcase classname="test.foo.bar.DefaultIntegrationTest" name="experimentsWithJavaElements" time="0.0" />
    <testcase classname="test.foo.bar.BundleResolverIntegrationTest" name="testGetBundle" time="0.0" />
    <testcase classname="test.foo.bar.BundleResolverIntegrationTest" name="testGetBundleLocation" time="0.656" />
    <testcase classname="test.foo.bar.ProjectSettingsTest" name="testNatureAddition" time="0.125" />
    <testcase classname="test.foo.bar.ProjectSettingsTest" name="testNatureRemoval" time="0.11" />
  </testsuite>
</testsuites>

上のXMLは jenkinsci に含まれてるテスト用の junit-report-1233.xml の抜粋ですが、このフォーマットはちょっときつい。testcase よりも前にある testsuite に実行したテスト数や失敗した数(tests, failures)がある。

XMLとしては、まあ妥当なものだとは思うのですが、この場合、下にある testcase を実行してから出ないと testsuite が生成できないということになります。shellspec のレポーターの設計は基本的に処理したものから随時出力することで素早いフィードバックを可能にしているのですが、このXMLの形式だと全てのテストを実行してからでないとレポートが生成できないということになります。

もちろんメモリやファイルにデータを貯めていって全部そろってからレポートを生成すればいいのですが、そうするとテスト実行中は真っ暗な画面のまま待っていなければいけないということです。それは寂しいので実行中は実行ログを表示させる?→あれ?それって標準のフォーマッター出力でよくね?と考えると、そもそも画面表示とテスト結果の保存は別々の機能なのではないのか?ということに思い至りました。

そこで今は次のようなオプションで考えています。

shellspec --format progress --out "junit:result-junit.xml" --out "cppunit:result-cppunit.xml" --out "html:result.html"

従来のフォーマッター(画面表示用)は一つだけ指定可能、テスト結果の保存は複数の形式で出力にしています。テストを再実行せずにHTMLやXMLで見たいということもありますしね。TAP形式ははたしてフォーマッターなのかテスト結果なのか悩むところはありますが。

それからもう一つ、テスト実行にかかった時間(例 time="107.824")にも悩んでいます。というか悩んでいました。POSXの範囲だと小数点以下の時間を取得するのが難しいからです。date コマンドはGNU拡張で取得できますがPOSIXの範囲ではできません。それにテスト毎に外部コマンドを実行するとパフォーマンスの低下に繋がります。そこで思いついたのが以下の方法です。

  1. shellspec 起動時にバックグラウンドプロセスを一つ作る
  2. このバックグラウンドプロセスは無限ループで i=$((i+1)) と数を数える
  3. このバックグラウンドプロセスはシグナルを受け取ると、現在の i を記録する
  4. 各テストの開始と終了時にバックグラウンドプロセスにシグナルを送信し現在の i を記録する
  5. テスト全体の実行時間は time コマンドで取得可能
  6. 記録された i より、各テストの実行時間が計算できる。

というバックグラウンドプロセス1つでCPUをぶん回して測定するという富豪的な解決方法です(笑)

というのは、JUnitの対応とは別にプロファイラ機能を作ろうかな?と考えてて思いついたものですが、ところでそもそもテストにプロファイラって必要なのでしょうか?通常プロファイラはアプリの遅いところ、ボトルネックを調べるためにありますが、アプリの実際の使用とテストとでは関数の使い方が違います。テストで遅いとわかったからと言ってそこを直せばアプリが早くなるわけでもなく、テスト実行でのプロファイラが役に立つことはあまりないでしょう。遅いテストを調べるという意味はありますが、テストが遅いのは問題ではありますが、それと修正すべきかどうか、修正できるかどうかは別の話です。アプリは問題ないがテストだけ遅いということもあるわけです。

そういうわけで考えてはいたが実装していなかった仕組みなんですが、JUnitでは、time属性があるんですよね?これ必須なんだろうか?

ここ によると required と書かれているんですが、こことか見ると optional なんですが?公式な仕様がないからわからん。

ということで対応はできるが面倒くさいなぁという話でした。shunit2 か bats が対応したらやろう