テスト

10章「テストと例外処理」のうち、テストに関する箇所をば。

手続きtest*

ちなみに、test* は以下でも出てきた。

評価結果が○○と等しくなるはずだ!ってな感じで、次のように使う。

gosh> (use gauche.test)
#<undef>
gosh> (test* "tested by equal?" '(bar baz) (cdr '(foo bar baz)))
test tested by equal?, expects (bar baz) ==> ok
#<undef>
gosh> (test* "tested by eq?" '(bar baz) (cdr '(foo bar baz)) eq?)
test tested by eq?, expects (bar baz) ==> ERROR: GOT (bar baz)
#<undef>

1つ目の評価は、gauche.testを使いますよー、というおまじない。includeとかusingとかimportとかそんな感じ?
2つ目の評価は、(cdr '(foo bar baz))は(bar baz)へ評価されることが期待されますよー、というテスト。第1引数はテストの説明。第2引数は第3引数を評価した結果。「第2引数を評価した結果」と「第3引数を評価した結果」はequal?で比較される。
3つ目の評価は、2つ目と似てるけど、「第2引数を評価した結果」と「第3引数を評価した結果」を比較するときに用いる比較のための述語を第4引数で指定できるバージョン。ここではeq?で比較しているから、等しいとは見なされず、テストをパスできなかった。

エラーになることをテスト

エラーになってしまうような式を第3引数に渡してやると、テストが中断されてしまうかと言うとそうではなくて、test*は第3引数の結果がエラーになるような場合は第3引数をtest-errorオブジェクトというものへ評価してくれるそうだ。

gosh> (test* "エラーになるはず" '() (cdr '()))
test エラーになるはず, expects () ==> ERROR: GOT #<<error> "pair required, but got ()">
#<undef>

エラーになるような式に対しては、第2引数で*test-error*を指定してやればテストにパスする。つまり、「ちゃんとエラーになるかどうか」もテストできる。こんな感じ。

gosh> (test* "エラーになるはず" *test-error* (cdr '()))
test エラーになるはず, expects #<error> ==> ok
#<undef>

エラーとなってしまうような式を「エラーになりましたよ」的なオブジェクトへ評価してしまうあたり、test*は手続きではなくマクロなんだろうな、たぶん。 ←確かにtest*はマクロだけど、その根拠は変だな...。なに言ってんだ、自分。手続きでも普通にできるよなぁ。

まとめてドン!

...とまぁこんな感じで対話的にやってもいいけど、テストのためのコードをまとめて書いておくこともできる。例えば「手続きtest*」の項の例をやってみると、

;; -*- coding: utf-8 -*-
;; test.scm
(use gauche.test)

(test-start "cdrのテストだよ!")

(test-section "セクション1")
(test* "tested by equal?" '(bar baz) (cdr '(foo bar baz)))
(test-section "セクション2")
(test* "tested by eq?" '(bar baz) (cdr '(foo bar baz)) eq?)

(test-end)

みたいなファイル"test.scm"を作っといて、対話的にloadしてやるか、gosh起動時にコマンドライン引数にこのファイルを指定して実行してやるかするとOK。以下はgoshで対話的にやった場合。

gosh> (load "./test.scm")
Testing cdrのテストだよ! ...                                           
<セクション1>-----------------------------------------------------------------------
test tested by equal?, expects (bar baz) ==> ok
<セクション2>-----------------------------------------------------------------------
test tested by eq?, expects (bar baz) ==> ERROR: GOT (bar baz)
failed.
discrepancies found.  Errors are:
test tested by eq?: expects (bar baz) => got (bar baz)
#t

手続き(かな?マクロかな?良く分からんけど)test-startとtest-endの間にtest*を書いときゃ良いわけね。
テストが膨大になる場合、テストログを見やすくするために、test-sectionで区切ってやるといい。

まとめてドドーン!

上の例の"test.scm"みたいなのをいくつか作っといて、Makefileでまとめてテストすることもできる。
たとえば、以下のようなMakefileを作っといて(test2.scmはcarのテスト)

check :
  rm -f test.log
  gosh test.scm >> test.log
  gosh test2.scm >> test.log

んで、make。

$ make -s check
Testing cdrのテストだよ! ...                                           failed.
discrepancies found.  Errors are:
test tested by eq?: expects (bar baz) => got (bar baz)
Testing carもテスト! ...                                             passed.

できたログファイルtest.logはこんなかんじ。

Testing cdrのテストだよ! ============================================================
<セクション1>-----------------------------------------------------------------------
test tested by equal?, expects (bar baz) ==> ok
<セクション2>-----------------------------------------------------------------------
test tested by eq?, expects (bar baz) ==> ERROR: GOT (bar baz)
failed.
discrepancies found.  Errors are:
test tested by eq?: expects (bar baz) => got (bar baz)
Testing carもテスト! ==============================================================
<section #1>-------------------------------------------------------------------
test tested by equal?, expects foo ==> ok
<section #2>-------------------------------------------------------------------
test tested by eq?, expects foo ==> ok
passed.

テストケースを可変に

今までのようにtest*をいくつか直に書いておくのもいいけど、例えばあるルールにそって1000回test*したい時とか、素直に1000個test*書くのもバカらしい。ってことで、test*を手続きで包んでやれば、テストケースを自動的に生成できる。
与えられたリストの最後のペアを求める以下のような手続きlast-pairに関してテストしたいとする。

gosh> (define last-pair
        (lambda (lis)
          (if (pair? (cdr lis))
              (last-pair (cdr lis))
              lis)))

以下のような手続きrun-last-testを用意してやって実行すればOK。

gosh> (use gauche.test)
#<undef>
gosh> (define run-last-test
        (lambda (p n)
          (let loop ((lis p) (i 0))
            (unless (>= i n)
              (test* (format "last-pair ~s" i) p (last-pair lis))
              (loop (cons i lis) (+ i 1))))))
run-last-test
gosh> (run-last-test '(a) 5)
test last-pair 0, expects (a) ==> ok
test last-pair 1, expects (a) ==> ok
test last-pair 2, expects (a) ==> ok
test last-pair 3, expects (a) ==> ok
test last-pair 4, expects (a) ==> ok
#<undef>

先頭にconsしていって次第に長いリストを作って、それに対してlast-pairしてるけど、何回consしたとしても結局末尾は変わらないから全部"ok"になってる。

test-start、test-endって何者?

テストに関していくつか方法が紹介されていたけど、最後のrun-last-testだけtest-startとtest-endと一緒に用いる例が紹介されていないのは何故だろう?できないから?
気になったのでやってみた。

gosh> (test-start "hoge")
Testing hoge ...                                                 
#<undef>
gosh> (run-last-test '(a) 5)
test last-pair 0, expects (a) ==> ok
test last-pair 1, expects (a) ==> ok
test last-pair 2, expects (a) ==> ok
test last-pair 3, expects (a) ==> ok
test last-pair 4, expects (a) ==> ok
#<undef>
gosh> (test-end)
passed.
0

結局、test-startとtest-endって何をやってんだろ?
今度はいきなりtest-endしてみた。

gosh> (test-end)
passed.
0

...ようわからん。ログの管理とか?