std:test — テストの構築と実行
標準ライブラリモジュール (index.md)。本書中の裸の §N は本書の節を指す。言語の意味論は ../language-spec.md を参照。テストランナー (hikari test) の収集・出力は ../test.md を参照。
import t := "std:test" は test / suite / timeout / run の 4 slot を持つ namespace object を t に束縛する。テストを値として構築し (test / suite / timeout)、それらを実行して集計する (run) — collect-then-run 型である。アサーションは別モジュール assert.md が担う。
ambient 束縛ではないため、REPL・playground・hikari run・hikari test のいずれからも同じ API を使える。
import { test, suite, run } := "std:test" import assert := "std:assert" report := run [ test "add" { assert.equal(1 + 1, 2) } suite "cmp" [ test "less" { assert.ok ("a" < "b") } test "equal" { assert.equal(7.compare 7, Equal) } ] ] report.passed #> 3 report.failed #> 0
| 名前 | 形 | 返り値 | 意味 |
|---|---|---|---|
test |
test("name", {block}) / test "name" {block} |
Trial | name (String) のテストを 1 件構築する。実行はしない |
suite |
suite("name", [trials]) |
Trial | 子 Trial (trials は Trial の List) をまとめたグループを構築する |
timeout |
timeout(ms, trial) |
Trial | trial の部分木に締切 ms (ミリ秒) を掛けた Trial を構築する。0 は無制限 (§4) |
run |
run([trials]) |
Report | Trial 列を記述順に実行し、集計した Report を返す。出力はしない |
本モジュールは自身では効果を持たない。test / suite は渡したブロックの効果を呼び出し元へ透過させる(EffectConduit。../language-spec.md §17.9)— 効果を持つテストを組めるのはこのためである。
1. Trial の構築 (test / suite / timeout)
test("name", {block})はnameとblockを保持する Trial 値を返す。blockは必須スロット 0 個の本体付きオブジェクト ({body}形) で、この時点では評価しない (実行は §2 のrun)。testは receiver を持ち回らない純関数である (可変セッションを共有しない)。どこで何度呼んでも、構築した Trial は他の Trial の実行に影響しない。等価は参照で見る —test(...)を 2 度書けば別の Trial で、==は偽になる (Rangeやハンドルと同じ扱い。../language-spec.md §9.1)。Trial は map のキーにできる。suite("name", [trials])は子 Trial をまとめたグループ Trial を返す。ネスト可能。実行時、子テスト名はname/子nameのようにプレフィックスされる。- Trial は
runに渡すための値であり、内部表現は不透明とする (name等のスロットに依存したコードは書かない)。 - Trial はCopyable である (../language-spec.md の複製の節)。不透明型だがホストの可変状態を持たない不変な値なので、複製は
Intを複製するのと同じ意味を持つ。 timeout(ms, trial)はtrialを包んだ締切つき Trial を返す。msは Int のミリ秒で、0は無制限を意味する。group を包めばその部分木の単体 Trial すべてにかかる。入れ子は内側が勝つ — 外側のtimeoutで包んだ group の中に別のtimeoutがあれば、その部分木には内側の値が効く。包むだけなのでtest/suiteの呼び出しの形は変わらない。Trialはstd:testの型メンバーとして export され、型注釈に書ける (t.Trial、またはimport { Trial } := "std:test")。不透明型であり内部表現は露出しない。
2. 実行と集計 (run)
run([trials])は Trial 列を記述順に実行し、Report を返す。- 各単体 Trial について、その
blockを評価する。結果が panic (Error) なら FAIL (メッセージと位置を記録)、それ以外は PASS。 - 合否境界は
runにある。1 件が FAIL しても残りの Trial は継続実行する。group Trial は子を再帰的に実行する。 runは各単体 Trial のblockの評価に要した実時間を測り、Report に載せる (§4)。- 各単体 Trial の
blockの評価には締切がかかる (§4)。締切を過ぎた Trial は FAIL として数え、残りの Trial は継続実行する。 runは出力を行わない (printer / reporter は将来課題)。整形出力は呼び出し側 (hikari testは test.md「出力と終了コード」 の形式で出力) が担う。
3. Report
run の返す Report は集計値で、少なくとも次のスロットを持つ closed・immutable なレコードである:
{
passed: Int
failed: Int
duration_ms: Int
trials: List({ name: String, passed: Bool, duration_ms: Int |})
failures: List({ name: String, message: String, pos: String |})
|}
passed/failed— PASS / FAIL したテスト件数。duration_ms— 実行した単体 Trial の実行時間の合計 (ミリ秒。§4)。trials— 実行した単体 Trial を実行順に並べたレコード List。nameは group プレフィックス込み、duration_msはその 1 件の実行時間 (§4)。group Trial 自身は行を持たない (子が持つ)。failures— FAIL した各テストのname(group プレフィックス込み)・失敗message・発生pos(file:line:col) のレコード List。PASS のみなら空 List。
Report は std:test の型メンバーとして export され、型注釈に書ける (../language-spec.md §13 のモジュール型 export、time.md §1 と対称)。import t := "std:test" のもとで r: t.Report と修飾参照するか、import { Report } := "std:test" で名前を取り出して r: Report と書く。不透明型の Trial と違い構造を持つ record 型なので、上のスロットは注釈越しに読める。
4. 実行時間とタイムアウト
4.1 計測
run は各単体 Trial の block の評価に要した実時間を測る。測るのは block の評価だけで、Trial の構築・収集・静的検査は含まない。値は §3 の trials[].duration_ms と、その合計である duration_ms に載る。run は測るだけで出力はしない (§2)。
4.2 締切
各単体 Trial には締切がかかる。既定は 60000 ミリ秒。timeout(ms, trial) で包んだ部分木はその値が優先し (§1)、hikari test は --timeout で既定そのものを差し替える (../test.md)。0 は無制限を意味する。
締切に達した Trial は FAIL になり、failures に次の形で載る。
message—timeout: exceeded <ms> mspos— 締切に達した時点で走っていた位置
FAIL の 1 種であって独立した転帰ではない。passed / failed を読む側が、増えた区分を知らないまま素通りすることを避けるためである。
4.3 締切が効かない区間
締切は実行系が制御を持っている間だけ効く。次の区間では締切を過ぎても停止せず、その区間を抜けて実行系へ制御が戻った時点で停止する。
- 組込の 1 呼び出しの内側
- ホストの I/O でブロックしている間 (
sleep・std:fs・std:process・未解決Futureの待ち)
したがってブロックしたまま戻らない試験は締切では止まらない。そこを止めたいなら、待つ側が Future の .timeout(ms) (../prelude.md §9.6) で待ち合わせに期限を与える。
5. hikari test との関係
hikari test は _test.hika の top-level に並べた bare な test "name" {block} が返す Trial 値を記述順に収集し、run で実行して Report を ../test.md「出力と終了コード」の形式で整形出力する (同「収集」)。hikari test は test / suite / timeout / assert をファイルスコープに供給するため、テストファイルでは import を省略できる (REPL・hikari run では明示 import が要る)。