# 测试
Deft 内置测试框架:`defsuite` 对测试分组,`deftest` 定义单个用例,
`assert/` 命名空间检查结果。`deft test` 命令运行匹配 `test_*.dft` 的
`.dft` 文件或显式指定的文件。
## 你好,测试
defsuite "arithmetic" { deftest "addition" { [assert/eq (1 + 2) 3] } deftest "multiplication" { [assert/eq (2 * 3) 6] } }
使用以下命令运行:
```
deft test test_math.dft
````
## 断言
每个断言都是 `assert/` 命名空间下独立的标准库函数 —— 没有类型关键字,
只有以斜杠分隔的名称:
[assert/eq $a $b] # equality [assert/ne $a $b] # inequality [assert/truthy $x] # truthy [assert/falsy $x] # falsy [assert/gt $a $b] # greater than [assert/gte $a $b] # greater than or equal [assert/lt $a $b] # less than [assert/lte $a $b] # less than or equal [assert/nil? $x] # nil check [assert/not-nil? $x] # not-nil check [assert/empty? $xs] # empty collection [assert/not-empty? $xs] # non-empty collection [assert/contains? $xs $v] # collection contains value [assert/type $x "number"] # type check [assert/length $xs $n] # length check
对于本身以 `?` 结尾的谓词,直接使用对应的 `assert/` 形式:
[assert/nil? $result] [assert/truthy [empty? $xs]]
### 自定义断言消息
可选最后一个参数是失败消息:
[assert/eq $status 200 "expected HTTP 200"]
## 测试发现
不带参数的 `deft test` 会在当前目录下查找 `test/test_*.dft`。显式传入
路径可运行子集:
```
deft test # all tests under ./test/
deft test test_sqlite.dft # one file
deft test test/test_sqlite.dft test/test_refs.dft # several
````
## 编写优秀的测试
### 对被测系统使用类型标注
def add {|a^number b^number| ($a + $b) } defsuite "add" { deftest "positive" { [assert/eq [add 1 2] 3] } deftest "negative" { [assert/eq [add -1 -2] -3] } deftest "zero" { [assert/eq [add 0 0] 0] } }
### 验证结果,而不只是成功
defsuite "json/parse" { deftest "valid" { set r [json/parse "{\"a\":1}"] [assert/eq $r~a 1] } deftest "invalid" { set r [json/parse "not json"] [assert/truthy [err? $r]] } }
### 测试抛出异常的代码
将调用包裹在 `try` 中,并断言 catch 分支已执行:
defsuite "divide" { deftest "by zero throws" { set caught false try { [divide 1 0] } catch _e { set caught true } [assert/truthy $caught] } }
### 高阶函数测试
defsuite "map" { deftest "applies f" { set result @{ 1 2 3 } |> map { $it * 2 } |> collect [assert/eq $result @{ 2 4 6 }] } deftest "lazy on infinite" { set result [range 0 1000] |> filter even? |> take 3 |> collect [assert/eq $result @{ 0 2 4 }] } }
### 管道测试
defsuite "pipeline" { deftest "chain" { set result @{ 1 2 3 4 } |> map { $it * $it } |> filter { $it > 4 } |> collect [assert/eq $result @{ 9 16 }] } }
### 用户自定义类型测试
deftype Point %{ :x %{:type :number} :y %{:type :number} } defsuite "Point" { deftest "construction" { set p %Point{ :x 3 :y 4 } [assert/eq $p~x 3] [assert/eq $p~y 4] } }
### 函数值测试
函数是一等公民;断言其返回值:
defsuite "factorial" { def factorial {|n| if ($n <= 1) { 1 } { ($n * [factorial ($n - 1)]) } } deftest "base case" { [assert/eq [factorial 0] 1] } deftest "recursive" { [assert/eq [factorial 5] 120] } }
## 基准测试
`defbench` 声明一个基准测试用例。使用 `deft test --bench` 运行:
defsuite "fib perf" { defbench "fib 20" { [fib 20] } }
运行器会报告每次迭代的墙钟时间。用它来发现热路径中的性能退化。
## 最佳实践
1. 每个测试套件聚焦一个概念。 按测试所针对的函数或类型分组,而不是
按输出形态分组。
2. 测试名称应像规格说明。 用 `"add positive numbers"`,而不是
`"test1"`。
3. 不要在测试之间共享可变状态。 每个 `deftest` 都应构造自己的
夹具(fixture)。
4. 优先使用纯断言,而非 `echo` 检查。 断言可由机器检查;打印输出
则不能。
5. 测试失败模式,而不仅是正常路径。 将调用包裹在 `try` 中,以验证
错误行为。
## CI 集成
典型的 GitHub Actions 步骤:
```yaml
- name: Run tests
run: |
zig build deft -Doptimize=ReleaseFast
./zig-out/bin/deft test
````
对于 FFI 测试:
```yaml
- name: Build with FFI
run: zig build deft -Dffi=true -Doptimize=ReleaseFast
- name: Run FFI tests
run: ./zig-out/bin/deft test test/test_ffi.dft
````
## 快速参考
| 形式 | 用途 |
|---|---|
| `defsuite "name" { ... }` | 对测试分组 |
| `deftest "name" { ... }` | 单个用例 |
| `defbench "name" { ... }` | 基准测试用例 |
| `[assert/eq actual expected]` | 断言 |
| `deft test [files...]` | 运行测试 |
| `deft test --bench` | 运行基准测试 |