本文へ移動
hikari 仕様

hikari テスト (hikari test) 仕様

本書は hikari test の仕様を定める。起動ディスパッチ全体は hikari-command.md、言語の意味論は language-spec.md、テストライブラリ本体の網羅仕様は std/test.md・std/assert.md を参照。

hikari のテストを hikari 自身で書く。*_test.hikari 内に test "name" {block} を並べ、assert 名前空間でアサーションする。CI やローカル開発での回帰検査を想定する。

test / asserthikari test 専用の魔法束縛ではなく、import で取得する通常のライブラリ (std:test / std:assert) である。hikari test はそれらで組んだテストを収集・実行する専用ランナーであり、REPL・playground・hikari run からも同じ API を使える。

構文

hikari test [path...] [--name <NAME>]
  • path は 0 個以上。各 path はファイルまたはディレクトリ。
  • ディレクトリは配下を再帰し、ファイル名が _test.hikari で終わるものを収集する。
  • ファイルはそのまま対象にする (名前が _test.hikari で終わらなくてよい)。
  • 引数省略時は cwd を対象ディレクトリとする。
  • 収集されたファイルはパス昇順で評価する。
  • --name <NAME> (--name=<NAME> 形式も可) を与えると、名前が `<NAME>` と一致するテストだけを実行する。値を伴わない --name は引数不正 (終了コード 2)。

テストライブラリ (std:test / std:assert)

テストの語彙は 2 つの標準ライブラリモジュールとして提供する。ambient 束縛ではないため、import すれば REPL・playground・hikari run を含む任意の文脈で使える。

std:assert — アサーション (状態なし)

equal / not_equal / ok / length / empty / fail を持つ純粋な名前空間。失敗時に panic (object.Error) を投げるだけで std:test に依存しない。test の外で失敗した場合は通常どおりトップレベルまで伝播する。

import assert := "std:assert"
assert.equal(1 + 1, 2)
pass 条件 失敗メッセージ例
assert.equal(actual, expected) actual == expected (language-spec.md §9.1。ユーザー定義 == スロットも尊重) assert.equal: expected 3, got 4
assert.not_equal(a, b) a == b が偽 assert.not_equal: expected values to differ, both 3
assert.ok(cond) condtrue (Bool、language-spec.md §9.3) assert.ok: expected true, got false
assert.fail(message) (常に失敗) message

両辺・実値の表示は各値の Inspect を用いる。

std:test — テストの構築と実行 (collect-then-run)

test は receiver を持ち回らない純関数で、テストを構築するだけ (実行しない)。実行・集計は run が担う。

import [test, run] := "std:test"
import assert := "std:assert"

run [
  test "add" { assert.equal(1 + 1, 2) }
  test "less" { assert.ok ("a" < "b") }
]
返り値 意味
test("name", {block}) / test "name" {block} Trial name (String) のテストを 1 件構築する。block は必須スロット 0 個の本体付きオブジェクト ({body} 形)。実行はしない
suite("name", [trials]) Trial 子 Trial をまとめたグループを構築する (ネスト可)。実行時に名前は 親/子 でプレフィックスする
run([trials]) Report Trial 列を記述順に実行し、集計した Report を返す。出力はしない

run の合否は各 Trial の block を呼んだ結果で決まる: panic (object.Error) なら FAIL (メッセージと位置を記録)、それ以外は PASS。1 件が失敗しても残りは継続実行する (合否境界は run にある)。group Trial は子を再帰実行する。

Report は集計値で、少なくとも次を持つ:

{
  passed := Int
  failed := Int
  failures := [{ name := String, message := String, pos := String |}]
|}

出力・整形は行わない (printer / reporter は将来課題)。hikari test は返った Report を「出力と終了コード」の形式で整形出力する。REPL では run の返り値をそのまま参照・表示すればよい。

収集 (hikari test の top-level Trial 収集)

hikari test は各対象ファイルについて次を行う:

  1. 静的検査 (check.md / static-analysis.md §1) を通す。
  2. test / suite (std:test) と assert (std:assert) をファイルスコープに供給して評価する (ゼロボイラープレート維持。REPL・playground・hikari run はこの供給を受けず import で明示取得する — 同一意味論・入口 2 つ)。
  3. ファイルの top-level が返す値のうち Trial であるものを記述順に収集する#{hikari} ブレース形式のファイル (テストファイルの通常形) では top-level の bare な式文 (test "name" {block} / suite …) が対象。#[hikari] ブラケット形式や | 区切りを使うファイルでは file-root の位置スロット (language-spec.md §1.4) も同じ枠で拾う。名前束縛 (t := …) の値は収集対象にしない (ヘルパ・import は束縛として共有する)。
  4. 収集した Trial 列を run で実行し、返った Report を「出力と終了コード」の形式で整形出力し、終了コードを立てる。

したがって _test.hikari は従来どおり top-level に test "name" {block} を並べるだけでよい。名前束縛 (ヘルパ定義・import した被テストコード) は従来どおり共有する。

名前フィルタ (--name)

--name <NAME> を与えると、収集した Trial を実行前に名前で枝刈りする。あるノードは、
自身の名前が `<NAME>` と一致すればその部分木ごと実行対象になる (単体テストなら 1 件、
suite なら配下すべて)。一致しない suite でも、子孫に一致があれば一致子孫だけを残して
実行する。単体テストで一致しなければ除外する。

同名が複数あればすべて実行する。一致 0 件なら 0 passed, 0 failed を出して正常終了 (0) する。
一致判定は各テストの名前の値 (test "…" に渡した文字列。エスケープ解決後) に対して行う。

分離 (isolation)

  • 各テストファイルは fresh な root 環境 で評価する (hikari <file> 1 回相当)。ファイル間で状態は共有しない。
  • ファイル評価はまず全 top-level 文を評価して Trial (thunk) を構築し、そのあと `run` が各 Trial の `block` を実行する。したがって top-level の副作用はテスト body より前に一度だけ起こり、各テストは共有 top-level 束縛 (ヘルパ定義・import した被テストコード) を参照する。テスト間の自動状態リセットは行わないため、共有された mutable 状態を変更すると後続テストに影響しうる。
  • 被テストコードは import <対象> := "<rel>" で取り込む (hikari-command.md §5.3 と同じ caller ディレクトリ基準の解決)。被テストコード側も必要なら import assert := "std:assert" してアサートヘルパを共有できる。

出力と終了コード

hikari testrun の返した Report をもとに、各ファイルについてファイル名見出しを出し、テストごとに PASS / FAIL 行 (FAIL は失敗メッセージと位置を続ける) を出力。最後に集計行 <P> passed, <F> failed を出す。出力例:

foo_test.hikari
  PASS  add は和を返す
  FAIL  add は0で恒等
        assert.equal: expected 5, got 6  (foo_test.hikari:8:3)

3 passed, 1 failed
状況 code
全テスト pass (対象 0 件を含む) 0
1 件でも FAIL / 構文・評価エラー 1
I/O エラー / 引数不正 2