Gauche:testについて
Shiro(2009/07/07 02:59:40 PDT): Shibuya.lisp TT#3の懇親会にて、gauche.testの機能追加の話がちょっと出たので考えてみた。 使い勝手が良くなるなら機能を足すこと自体は問題ない。ただ、やたらにAPIを増やすのは却ってわかりづらいので、組み合わせで済むものなら(よっぽど便利にならない限りは)足さないんじゃないかな、というのはある。 なので、私があまり積極的でない機能を足してほしいという方は、「こういう理由で便利なんだよ!」と私を説得してくださいな。
入れたいな、という機能
異常系のテストで、想定したconditionが上がっていることを簡単にテストしたい
0.8.14までは<test-error>オブジェクトのひとつのインスタンスが*test-error* に束縛されてて、結果として上がってくるものが<test-error>かどうかを 調べる、という形が推奨だったんですが:
(test* name *test-error* エラーを上げる式)
これには2つ欠点がありました。
- エラーをすべて一律に扱うので、期待したエラーが上がっているのか 別のところでエラーが上がってるのか区別できない。期待したconditionかどうかを テストするには自前でguardを書いてやる必要があり、手間がかかる:
(test* name <read-error> (guard (e (else (class-of e))) expr))
- この書法は、「すべての<test-error>オブジェクト同士は常にequal?である」 という一種のハックに依存しているんだけど、それがちょっと気持ち悪い。
これについては既に次のように改善してtrunkにコミットしてあります。
- (test-error &optional condition-type) という手続きで、 新しい<test-error>オブジェクトを生成できる。
- test関数、test*マクロのoptionalな手続きを「比較」ではなく「チェック手続き」という セマンティクスにした。「チェック手続き」は常にexpectedとテスト式の結果を この順で受け取り、結果がexpectedに「沿って」いれば#tを返す。 <test-error>オブジェクト以外のものに対するデフォルトのチェック手続きはequal? なんだけど、このセマンティクスだとexpectedと結果が(equal?のように)対等ではない、 というのがポイント。
- <test-error>オブジェクトのチェック手続きは次のセマンティクスを持つ:
「expectedと結果の双方が<test-error>オブジェクトで、 expectedの方のcondition-typeが無指定か、結果のcondition-typeの スーパータイプなら#t」
これを使うと、例えばexprが<read-error>を上げるかどうかはこう書ける:
(test* name (test-error <read-error>) expr)
exprが上げるのは<read-error>のサブクラスであっても構いません。
expected failure
failすることが期待されるテストって矛盾してるじゃん、 そもそも期待されてるなら、その結果をexpectすれば単なるsuccessになるから 意味ないじゃん、と思っていたのですが、 「環境によってはfailする場合がある」というテストを 書いておくのに便利だな、ということに気づきました。
lint的なチェック
これはtest-moduleの発展形なんだけれど、 koguroさんの[glint http://homepage.mac.com/naoki.koguro/prog/codecheck/index-j.html]的 なことをテスト時にやれないか、ということです。
Gaucheでは実行時のon-the-flyのコンパイル時間が短いことも重要なので、 実行時compileであまり重いチェックが出来ません。 でもテスト時なら少々時間を食ってもいいので、 グローバルな依存関係を見れば、手続きの引数の違いや 型の違いなどまである程度追えるんじゃないかと思います。
結果表示時に、長い表示をtruncate
テストが失敗した場合、標準出力に出るサマリに失敗した結果が含まれますが ("discrepancy found" のところ)、実行結果が巨大だとこれがかなり見にくいです。 例えば大きなSXMLを生成するのとか。Common Lispの省略記法みたいな感じで 短縮できるといいかな、と思ってます。 必要があればログを見てもらうと全部出てる、ということで。
入れるかも、という機能
部分的にテストを走らせる
特定のfailureだけ潰している時は、テスト全部を毎回走らせるのは 重いことがあります。
指定した文字列やregexpにマッチする名前を持つ test-sectionだけ走らせる、というのはできるかな、と思ってます。
テストの前処理や後処理に副作用を使ってる場合、 それがトップレベルにべたで書いてあると選択的に実行するのが難しくなりますが、 そのへんも含めて構造化する構文を導入すべきか、 それともトップレベルの実行を何らかの方法でハックするか、というのは 思案中ですが。
嬉しさがよくわからないので入れないだろうな、という機能
結果が#tであること、#fであること、などをテストする手続き
JUnitとかにはassertTrueとかassertFalseとかありますが、Schemeのような 無名関数が簡単にかける言語でこれらを使うメリットがよくわからないんですね。 というのは、
- exprが#tになることをテストする(test-true name expr)という 手続きは (test* name #t expr) と書いてもほとんど同じ
- test-trueでは、テストが失敗したときに「それが失敗したこと」しかわからない。 exprの中で「あるアクションの結果が期待したナニカに沿っているかどうか」を 判断して#t/#fを返しているなら、それはむしろ次のように と書くべきで、これなら失敗した時に結果を返す式の結果がログに出るので 原因究明がしやすい:
(test* name ナニカ 結果を返す式 rがナニカに沿っているかどうかテストする手続き)
で、JavaやC++ではこのrがナニカに沿っているかどうかテストする手続きを さらっと書くのが面倒だからわざわざassertTrueが用意してあるんじゃないか、 と私は思っていましたよ。
今のGaucheでも、
(test* name #t test-expr)
と書くのは本来は述語をテストしたい場合だけに止め、できる限りexpectedの ところには具体的な値を書くべきじゃないか、と思ってます。つい#tにしたくなっちゃう 気持ちはわかるし、私もそういうテストをたくさん書いてるんですが、 例えばmy-funcの戻り値が文字列であることをテストしたいなら、
(test* name #t (string? (my-func)))
ではなく、
(test* name <string> (class-of (my-func)))
あるいは
(test* name <string> (my-func) (lambda (t r) (is-a? r t))
と書くのがいいんじゃないかと。
で、これだと長くなっちゃうんで、最後のやつをマクロにして
(test-type* name type expr)
と書けるようにする、というのはありだと思います。
kou (2009/07/07 04:39:20):
kou (2009/07/07 04:42:37):
shiro (2009/07/07 05:10:03):
shiro (2009/07/07 05:15:20):
koguro (2009/07/07 06:32:59):
higepon (2009/07/07 07:44:53):
naoya_t (2009/07/07 08:36:48):
shiro (2009/07/07 15:44:27):
koguro (2009/07/08 06:04:29):