表題

テストスイートのための Scheme API

著者

Per Bothner <per@bothner.com>

状態

This SRFI is currently in ``final'' status. To see an explanation of each status that a SRFI can hold, see here. It will remain in draft status until 2005/03/17, or as amended. To provide input on this SRFI, please mailto:srfi minus 64 at srfi dot schemers dot org. See instructions here to subscribe to the list. You can access previous messages via the archive of the mailing list.

概要

この SRFI ではテストスイート (test suite) を書くための API を定義し、 Scheme API、ライブラリ、アプリケーション、および実装のテストの移植性を提供し、簡単化する。 テストスイートとは、1つのテストランナー (test-runner) のコンテキストの下で実行されるテストケース (test case) の集合である。 テストスイートの実行結果の報告や処理をカスタマイズできるようにするために、 この SRFI では、新しいテストランナーを書くための支援(★) も行う。

論拠

Scheme コミュニティはテストスイートを書くための標準を必要としている。 すべての SRFI や他のライブラリはテストスイートと共に提供されるべきである。 そのようなテストスイートは、 モジュールのような非標準の機能を必要とせず、 移植性がなければならない。 テストスイートの実装、つまり、「ランナー」(runner) は、移植性がある必要はないが、 移植性のある実装を書けるほうが望ましい。

Scheme で書かれた別のテストフレームワークも存在する。 たとえば SchemeUnit がそれである。 しかし、SchemeUnit は移植性がない。 それに少し冗長な側面がある。 SchemeUnit のテストがこの SRFI のフレームワークの下で実行できるように、 あるいは、その逆が実行できるように、 このフレームワークと SchemeUnit との間のブリッジがあると便利かも知れない。 また、 Java 標準の JUnit API への Scheme インターフェイスを提供する Scheme ラッパーも存在する。 このフレームワークを使用して書かれたテストを JUnit ランナーの下で 実行させることができるブリッジがあると便利かも知れない。 しかし、これらの機能はいずれもこの SRFI の仕様の一部ではない。

この API は暗黙的な動的状態を使用している。 たとえば暗黙のテストランナーを使用している。 これにより、この API は簡便で明瞭なものになっている。 しかし、JUnit スタイルのフレームワークのように、 明示的にテスト オブジェクトを使用する方法に比べて、 少しエレガントではなく、構成的 (compositional) であるかも知れない。 オブジェクト指向あるいは関数型のいずれの設計原則に従うことも主張しないが、 便利で拡張可能な設計にすることを望んでいる。

この SRFI では、 少しのマクロを追加するだけで、 Scheme ソースファイルからテストスイートに変換することができる。 一からファイルを書く必要はなく、したがって、再インデントする必要もない。

この SRFI で定義されるすべての名前は test- というプレフィックスで始まる。 関数のようなフォームはすべて構文として定義される。 それらは関数やマクロ、あるいは、組み込みの関数やマクロとして実装してもよい。 それらを構文として定義している理由は、構文を使えば、 部分式を評価せずに特定のテストをスキップすることができたり、 行番号を表示したりや例外をキャッチしたりと言った機能を追加できるからである。

仕様

この SRFI はある程度複雑な仕様ではあるが、 以下の最初のいくつかの節を読めば、 簡単なテストスイートを書くことができるようになるだろう。 カスタムのテストランナーを書くというような、 より高度な機能については、最後に記述する。

基本的なテストスイートを書く

簡単な例から始めよう。 これは完全に自己完結したテストスイートである。

;; テストスイートを初期化して名前を付ける。
(test-begin "vec-test")
(define v (make-vector 5 99))
;; 式を評価したら真になることを検査する
(test-assert (vector? v))
;; 式が eqv? の意味で別の式に等価であることを検査する
(test-eqv 99 (vector-ref v 2))
(vector-set! v 2 7)
(test-eqv 7 (vector-ref v 2))
;; テストスイートを終了し、テスト結果を報告する。
(test-end "vec-test")

このテストスイートは自身のソースファイル内に記述することができる。 他に必要なものはない。 トップレベル フォームを必要としないので、 インデントしなくても、 既存のプログラムやテストをラップすることができる。 個々のテストに名前を付けなくてもよいので (オプションで名前を付けることはできるが)、 新しいテストを追加することも容易である。

テストケースはテストランナーのコンテキストの下で実行される。 テストランナーはテスト結果を蓄積して報告するためのオブジェクトである。 この SRFI では、カスタムのテストランナーを作成して使用する方法を定義している。 しかし、この SRFI の実では、デフォルトのテストランナーも提供すべきである。 この SRFI では次のことを提案する (しかし要求はしない)。 上記のファイルをトップレベル環境にロードすると、 実装固有のデフォルトのテストランナーによってテストが実行され、 test-end によって実装固有の方法でテスト結果が表示される。

単純なテストケース

次のフォームは、与えられた条件が真であるかどうかを検査するための、 プリミティブなテストケースである。 テストケースには名前を付けることができる。

(test-assert [test-name] expression)

このフォームは expression を評価する。 その結果が真であれば、検査は合格である。 結果が偽であれば、検査が失敗したことを報告する。 例外が発生した場合も検査は失敗する。 このことは、Scheme 実装が例外をキャッチする手段を備えていることを仮定している。 検査が失敗したときに、それがどのように報告されるかは、 テストランナーの環境に依存する。 test-name 引数にはテストケースの名前の文字列を指定する。 (例では test-name は文字列リテラルを記述しているが、 これは式であり、1度だけ評価される。) エラーを報告するとき、この名前が使用される。 後で述べるように、テストをスキップするときにも、この名前が使用される。 カレントのテストランナーが存在しないときに test-assert を呼び出すことはエラーである。

test-assert を直接使用するよりも、 以下のフォームを使うほうが便利である。

(test-eqv [test-name] expected test-expr)

これは次のフォームと等価である。

(test-assert [test-name] (eqv? expected test-expr))

同様に test-equaltest-eqtest-assertequal?eq? を組み合わせたフォームと等価である。

(test-equal [test-name] expected test-expr)
(test-eq [test-name] expected test-expr)

簡単な例を挙げよう。

(define (mean x y) (/ (+ x y) 2.0))
(test-eqv 4 (mean 3 5))

不正確な実数の近似的な等値を検査するには、 test-approximate を使用します。

(test-approximate [test-name] expected test-expr error)

これは次のフォームと等価である (ただし、上記のフォームでは引数が1度だけ評価される点が異なる)。

(test-assert [test-name]
  (and (>= test-expr (- expected error))
       (<= test-expr (+ expected error))))

エラーをキャッチするテスト

We need a way to specify that evaluation should fail. This verifies that errors are detected when required.

(test-error [[test-name] error-type] test-expr)

test-expr を評価するとエラーが発生することを検査する。 エラーの種類は error-type で指定する。

error-type を省略するか #t を指定した場合は、 「何らかのエラーが発生する」ということを意味する。 たとえば、

(test-error #t (vector-ref '#(1 2) 9))

この SRFI では、 test-error の形式については規定せず、実装固有の仕様とする (あるいは将来の SRFI で規定されるものとする)。 しかし、どのような実装でも、 #t をサポートしなければならない。 実装によっては 「SRFI-35: コンディション」 をサポートしているかも知れないが、これは 「SRFI-36: I/O コンディション」 で標準化されているのみであり、SRFI-36 はテストスイートには役に立たないだろう。 実装固有の例外型をサポートしてもよい。 たとえば、Java ベースの実装では、 Java 例外クラスの名前を指定できるようにしてもよい。

;; Kawa 固有の例
(test-error <java.lang.IndexOutOfBoundsException> (vector-ref '#(1 2) 9))

例外をキャッチできない Scheme 処理系では、 test-error フォームは無視すればよい。

テスト構文

テスト構文 (test syntax) はトリッキーである。 不正な構文に対してエラーが発生することを検査したい場合は特にそうである。 The following utility function can help:

(test-read-eval-string string)

この関数は (read を呼び出すことで) string を解析し、その結果を評価する。 その評価結果が test-read-eval-string から返される。 read を呼び出した後に読み取られていない文字が存在するならば、 エラーを発生させる。 たとえば:
(test-read-eval-string "(+ 3 4)") を評価すると 7 になる
(test-read-eval-string "(+ 3 4") はエラーを発生させる
(test-read-eval-string "(+ 3 4) ") はエラーを発生させる。 リストを読み取った後に余分なゴミ (つまりスペース) が残るからである。

test-read-eval-string を使用したテストを以下に示す。

(test-equal 7 (test-read-eval-string "(+ 3 4)"))
(test-error (test-read-eval-string "(+ 3"))
(test-equal #\newline (test-read-eval-string "#\\newline"))
(test-error (test-read-eval-string "#\\newlin"))

;; srfi-62 が使用可能でなければ次の 2 つのテストをスキップする。
(test-skip (cond-expand (srfi-62 0) (else 2)))
(test-equal 5 (test-read-eval-string "(+ 1 #;(* 2 3) 4)"))
(test-equal '(x z) (test-read-string "(list 'x #;'y 'z)"))

テストグループとパス

テストグループ (test group) とは、テストケース、式、定義、を含むフォームの名前付きシーケンスである。 グループに入ると、 テストグループ名 (test group name) が設定される。 グループを抜けると、 直前のグループ名が復元される。 これは動的な (実行時の) 処理であり、 グループはこれ以外に効果を持たない。 テストグループとは非形式的なグループ化である。 Scheme 値でもなければ構文フォームでもない。

テストグループは、ネストされた内部のテストグループを含むことができる。 テストグループ パス (test group path) とは、現在アクティブな (現在入っている) テストグループの 名前のリストであり、最も古い (最も外側の) テストグループを最初に記述する。

(test-begin suite-name [count])

test-begin は新しいテストグループに入る。 suite-name 引数が現在のテストグループ名になり、 テストグループ パスの最後に追加される。 移植性のあるテストスイートでは、 suite-name に文字列リテラルを指定すべきである。 式や他の種類のリテラルを指定した場合の効果は不定である。

論拠: シンボルを指定するほうが、いくつかの点において好ましいだろう。 しかし、我々は人間が読める名前を付けたいのだが、 標準 Scheme ではリテラル シンボルにスペースや大小文字を区別するテキストを 含める方法を提供していない。

オプションの count 引数には、 このグループによって実行されるテストケースの個数を指定する (ネストされたテストグループは、1つのテストケースとして数える)。 ここで指定された個数より余分なテストは実行されない。 ある種のエラーが発生したためにテストを実行することができない場合に対処するために、 この仕組みを使うことができる。

さらに、現在実行中のテストランナーが存在しない場合は、 実装に固有の方法で、テストランナーがインストールされる。

(test-end [suite-name])

test-end は現在のテストグループを抜けます。 suite-name が現在のテストグループの名前に一致しない場合は、エラーになる。

さらに、対応する test-begin が新しいテストランナーをインストールした場合は、 test-end は実装固有の方法でテスト結果を報告した後、 そのテストランナーをアンインストールする。

(test-group suite-name decl-or-expr ...)

これは次のフォームと等価である。

(if (not (test-to-skip% suite-name))
  (dynamic-wind
    (lambda () (test-begin suite-name))
    (lambda () decl-or-expr ...)
    (lambda () (test-end suite-name))))

通常これは、名前付きテストグループの中で decl-or-expr を実行することと等価である。 しかし、グループが test-skip (後述) に一致した場合は、グループ全体がスキップされる。 また、例外が発生した場合は、 test-end が実行される。

セットアップとクリーンアップの処理

(test-group-with-cleanup suite-name
  decl-or-expr ...
  cleanup-form)

decl-or-expr フォームを (<body> のように) 順に実行し、 最後に cleanup-form を実行する。 decl-or-expr の1つが例外をレイズした場合でも、 cleanup-form は必ず実行される (ここでは Scheme 実装が例外をキャッチする方法を提供していることを仮定している)。

例:

(test-group-with-cleanup "test-file"
  (define f (open-output-file "log"))
  (do-a-bunch-of-tests f)
  (close-output-port f))

条件付きテストスイート、および、その他の高度な機能

以下では、どのテストを実行すべきかを制御するための機能、つまり、 どのテストが失敗すると予想されるかを指定するための機能について述べる。

テスト指定子

ある特定のテストだけを実行したいことがあったり、 ある特定のテストは失敗することが分かっていることがあるだろう。 テスト指定子 (test specifier) とはテストランナーを引数として受け取りブール値を返す関数である。 テスト指定子は、テストを実行する前に呼び出され、 そのテストを実行するかどうかを決定する。 テスト指定子を簡単に指定するために、 手続きでない値をテスト指定子に自動変換する仕組みも提供する。 以下に述べる countname がそれである。

簡単な例:

(if some-condition
  (test-skip 2)) ;; 次の2つのテストをスキップする

(test-match-name name)
この構文により作成されるテスト指定子は、 現在のテスト名 (test-runner-test-name で返される値) が name 引数に equals? の意味で等しいときに真を返す。

(test-match-nth n [count])
この構文は状態を記憶する述語を返す。 カウンタが内蔵されており、何回この述語が呼び出されたかを記憶している。 この述語は、n 回目に呼び出したときと、 それに引き続く (- count 1) 回の呼び出し時にのみ真を返す (最初の呼び出しを 1 回目とする)。 count 引数のデフォルト値は 1 である。

(test-match-any specifier ...)
この構文が返すテスト指定子は specifier 引数のいずれかが真を返すときに真を返す。 各 specifier は順に適用される。 そのため、前の specifier が真を返すとしても、後の specifier は副作用の効果を持つことになる。

(test-match-all specifier ...)
この構文が返す手続きはすべての specifier 引数が真を返すときに真を返す。 各 specifier は順に適用される。 そのため、前の specifier が偽を返すとしても、後の specifier は副作用の効果を持つことになる。

count (つまり整数)
(test-match-nth 1 count) に対する省略形である。

name (つまり文字列)
(test-match-name name) に対する省略形である。

特定のテストのスキップ

特定のテストをスキップしたいこともあるだろう。

(test-skip specifier)

test-skip を評価すると、 テスト指定子 specifier を現在アクティブなスキップ指定子 (skip-specifier) の集合に追加する。 各テスト (あるいは test-group) を実行する前に、アクティブなスキップ指定子の集合を アクティブなテストランナーに適用する。 そして、スキップ指定子の集合のいずれかが真を返す場合、 そのテストはスキップされる。

記法を簡便にするために、 specifier が文字列の場合は、 それは (test-match-name specifier) への構文シュガーとなる。 例:

(test-skip "test-b")
(test-assert "test-a")   ;; 実行される
(test-assert "test-b")   ;; スキップされる

(test-begin "group1")
(test-skip "test-a")
(test-assert "test-a")   ;; スキップされる
(test-end "group1")      ;; 上記の test-skip はアンドゥされる
(test-assert "test-a")   ;; 実行される

予期される失敗

ときとして、テストケースが失敗することが予め予期できることがある。 しかし、それを修正する時間がないとしよう。 多分、ある種の機能がある種のプラットフォームでしか動作しないためだろう。 しかし、このテストケースはそのまま残しておき、 その不具合を覚えておきたいだろう。 そのようなテストケースは、失敗することが予期されることを記録しておきたい。

(test-expect-fail specifier)

Matching tests (where matching is defined as in test-skip) are expected to fail. This only affects test reporting, not test execution. For example: テスト指定子が真を返す場合、そのテストは失敗することが予期されるものとして扱われる これは、テスト結果の報告に影響するのであって、 テストの実行に影響するのではないことに注意せよ。

(test-expect-fail 2)
(test-eqv ...) ;; 失敗することが予期される
(test-eqv ...) ;; 失敗することが予期される
(test-eqv ...) ;; 合格することが予期される

テストランナー

テストランナー (test-runner) とはテストスイートを実行し、状態を管理するためのオブジェクトである。 テストグループ パス、スキップ指定子の集合、予期される失敗の指定子の集合は、 テストランナーの一部である。 テストランナーは通常、実行されたテストに関する統計を収集する機能も持つ。

(test-runner? value)
value がテストランナーであるか判定する。

(test-runner-current)
(test-runner-current runner)
カレントのテストランナーを取得および設定する。 実装が (SRFI-39 のような) パラメータ オブジェクトをサポートしている場合は、 test-runner-current はパラメータ オブジェクトでもよい。 別の方法として、 test-runner-current はマクロや★ Alternatively, test-runner-current may be implemented as a macro or function that uses a fluid or thread-local variable, or a plain global variable.

(test-runner-get)
(test-runner-current) と同じだが、 カレントのテストランナーが存在しない場合は、例外を投げる。

(test-runner-simple)
単純なテストランナーを作成する。 単純なテストランナーは、標準出力ポートにエラーとテスト結果の要約を表示する。

(test-runner-null)
テスト結果に対して何も行わないテストランナーを作成する。 カスタムのテストランナーを書く際に、 このテストランナーを拡張するために用意されている。

Implementations may provide other test-runners, perhaps a (test-runner-gui).

(test-runner-create)
Create a new test-runner. Equivalent to ((test-runner-factory)).

(test-runner-factory)
(test-runner-factory factory)
Get or set the current test-runner factory. A factory is a zero-argument function that creates a new test-runner. The default value is test-runner-simple, but implementations may provide a way to override the default. As with test-runner-current, this may be a parameter object, or use a per-thread, fluid, or global variable.

Running specific tests with a specified runner

(test-apply [runner] specifier ... procedure)
Calls procedure with no arguments using the specified runner as the current test-runner. If runner is omitted, then (test-runner-current) is used. (If there is no current runner, one is created as in test-begin.) If one or more specifiers are listed then only tests matching the specifiers are executed. A specifier has the same form as one used for test-skip. A test is executed if it matches any of the specifiers in the test-apply and does not match any active test-skip specifiers.

(test-with-runner runner decl-or-expr ...)
Executes each decl-or-expr in order in a context where the current test-runner is runner.

Test results

Running a test sets various status properties in the current test-runner. This can be examined by a custom test-runner, or (more rarely) in a test-suite.

Result kind

Running a test may yield one of the following status symbols:

'pass
The passed, as expected.
'fail
The test failed (and was not expected to).
'xfail
The test failed and was expected to.
'xpass
The test passed, but was expected to fail.
'skip
The test was skipped.

(test-result-kind [runner])
Return one of the above result codes from the most recent tests. Returns #f if no tests have been run yet. If we've started on a new test, but don't have a result yet, then the result kind is 'xfail is the test is expected to fail, 'skip is the test is supposed to be skipped, or #f otherwise.

(test-passed? [runner])
True if the value of (test-result-kind [runner]) is one of 'pass or 'xpass. This is a convenient shorthand that might be useful in a test suite to only run certain tests if the previous test passed.

Test result properties

A test runner also maintains a set of more detailed result properties associated with the current or most recent test. (I.e. the properties of the most recent test are available as long as a new test hasn't started.) Each property has a name (a symbol) and a value (any value). Some properties are standard or set by the implementation; implementations can add more.

(test-result-ref runner 'pname [default])
Returns the property value associated with the pname property name. If there is no value associated with 'pname return default, or #f if default isn't specified.

(test-result-set! runner 'pname value)
Sets the property value associated with the pname property name to value. Usually implementation code should call this function, but it may be useful for a custom test-runner to add extra properties.

(test-result-remove runner 'pname)
Remove the property with the name 'pname.

(test-result-clear runner)
Remove all result properties. The implementation automatically calls test-result-clear at the start of a test-assert and similar procedures.

(test-result-alist runner)
Returns an association list of the current result properties. It is unspecified if the result shares state with the test-runner. The result should not be modified, on the other hand the result may be implicitly modified by future test-result-set! or test-result-remove calls. However, A test-result-clear does not modify the returned alist. Thus you can archive result objects from previous runs.

Standard result properties

The set of available result properties is implementation-specific. However, it is suggested that the following might be provided:

'result-kind
The result kind, as defined previously. This is the only mandatory result property.
(test-result-kind runner) is equivalent to:
(test-result-ref runner 'result-kind)
'source-file
'source-line
If known, the location of test statements (such as test-assert) in test suite source code..
'source-form
The source form, if meaningful and known.
'expected-value
The expected non-error result, if meaningful and known.
'expected-error
The error-type specified in a test-error, if it meaningful and known.
'actual-value
The actual non-error result value, if meaningful and known.
'actual-error
The error value, if an error was signalled and it is known. The actual error value is implementation-defined.

Writing a new test-runner

This section specifies how to write a test-runner. It can be ignored if you just want to write test-cases.

Call-back functions

These call-back functions are methods (in the object-oriented sense) of a test-runner. A method test-runner-on-event is called by the implementation when event happens.

To define (set) the callback function for event use the following expression. (This is normally done when initializing a test-runner.)
(test-runner-on-event! runner event-function)

An event-function takes a test-runner argument, and possibly other arguments, depending on the event.

To extract (get) the callback function for event do this:
(test-runner-on-event runner)

To extract call the callback function for event use the following expression. (This is normally done by the implementation core.)
((test-runner-on-event runner) runner other-args ...)

The following call-back hooks are available.

(test-runner-on-test-begin runner)
(test-runner-on-test-begin! runner on-test-begin-function)
(on-test-begin-function runner)
The on-test-begin-function is called at the start of an individual testcase, before the test expression (and expected value) are evaluated.

(test-runner-on-test-end runner)
(test-runner-on-test-end! runner on-test-end-function)
(on-test-end-function runner)
The on-test-end-function is called at the end of an individual testcase, when the result of the test is available.

(test-runner-on-group-begin runner)
(test-runner-on-group-begin! runner on-group-begin-function)
(on-group-begin-function runner suite-name count)
The on-group-begin-function is called by a test-begin, including at the start of a test-group. The suite-name is a Scheme string, and count is an integer or #f.

(test-runner-on-group-end runner)
(test-runner-on-group-end! runner on-group-end-function)
(on-group-end-function runner)
The on-group-end-function is called by a test-end, including at the end of a test-group.

(test-runner-on-bad-count runner)
(test-runner-on-bad-count! runner on-bad-count-function)
(on-bad-count-function runner actual-count expected-count)
Called from test-end (before the on-group-end-function is called) if an expected-count was specified by the matching test-begin and the expected-count does not match the actual-count of tests actually executed or skipped.

(test-runner-on-base-end-name runner)
(test-runner-on-bad-end-name! runner on-bad-end-name-function)
(on-bad-end-name-function runner begin-name end-name)
Called from test-end (before the on-group-end-function is called) if a suite-name was specified, and it did not that the name in the matching test-begin.

(test-runner-on-final runner)
(test-runner-on-final! runner on-final-function)
(on-final-function runner)
The on-final-function takes one parameter (a test-runner) and typically displays a summary (count) of the tests. The on-final-function is called after called the on-group-end-function correspondiong to the outermost test-end. The default value is test-on-final-simple which writes to the standard output port the number of tests of the various kinds.

The default test-runner returned by test-runner-simple uses the following call-back functions:
(test-on-test-begin-simple runner)
(test-on-test-end-simple runner)
(test-on-group-begin-simple runner suite-name count)
(test-on-group-end-simple runner)
(test-on-bad-count-simple runner actual-count expected-count)
(test-on-bad-end-name-simple runner begin-name end-name)
You can call those if you want to write a your own test-runner.

Test-runner components

The following functions are for accessing the other components of a test-runner. They would normally only be used to write a new test-runner or a match-predicate.

(test-runner-pass-count runner)
Returns the number of tests that passed, and were expected to pass.

(test-runner-fail-count runner)
Returns the number of tests that failed, but were expected to pass.

(test-runner-xpass-count runner)
Returns the number of tests that passed, but were expected to fail.

(test-runner-xfail-count runner)
Returns the number of tests that failed, and were expected to pass.

(test-runner-skip-count runner)
Returns the number of tests or test groups that were skipped.

(test-runner-test-name runner)
Returns the name of the current test or test group, as a string. During execution of test-begin this is the name of the test group; during the execution of an actual test, this is the name of the test-case. If no name was specified, the name is the empty string.

(test-runner-group-path runner)
A list of names of groups we're nested in, with the outermost group first.

(test-runner-group-stack runner)
A list of names of groups we're nested in, with the outermost group last. (This is more efficient than test-runner-group-path, since it doesn't require any copying.)

(test-runner-aux-value runner)
(test-runner-aux-value! runner on-test)
Get or set the aux-value field of a test-runner. This field is not used by this API or the test-runner-simple test-runner, but may be used by custom test-runners to store extra state.

(test-runner-reset runner)
Resets the state of the runner to its initial state.

Example

This is an example of a simple custom test-runner. Loading this program before running a test-suite will install it as the default test runner.

(define (my-simple-runner filename)
  (let ((runner (test-runner-null))
	(port (open-output-file filename))
        (num-passed 0)
        (num-failed 0))
    (test-runner-on-test! runner
      (lambda (runner result)
        (case (cdr (assq 'result-kind result))
          ((pass xpass) (set! num-passed (+ num-passed 1)))
          ((fail xfail) (set! num-failed (+ num-failed 1)))
          (else #t))))
    (test-runner-on-final! runner
       (lambda (runner)
          (format port "Passing tests: ~d.~%Failing tests: ~d.~%"
                  num-passed num-failed)
	  (close-output-port port)))
    runner))

(test-runner-factory
 (lambda () (my-simple-runner "/tmp/my-test.log")))

Implementation

The test implementation uses cond-expand (SRFI-0) to select different code depending on certain SRFI names (srfi-9, srfi-34, srfi-35, srfi-39), or implementations (kawa). It should otherwise be portable to any R5RS implementation.

testing.scm

Examples

Here is srfi-25-test.scm, based converted from Jussi Piitulainen's test.scm for SRFI-25.

Test suite

Of course we need a test suite for the testing framework itself. This suite srfi-64-test.scm was contributed by Donovan Kolbly <donovan@rscheme.org>.

Copyright

Copyright (C) Per Bothner (2005, 2006)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Author: Per Bothner
Editor: Francisco Solsona

Last modified: Sun Jan 28 13:40:18 MET 2007