Nim 完全指南 / 19 测试与质量
第 19 章:测试与质量
19.1 unittest 模块
Nim 内置 unittest 模块,提供完整的测试框架:
import std/unittest
# 基本测试
suite "数学运算":
test "加法":
check(1 + 1 == 2)
test "减法":
check(5 - 3 == 2)
test "乘法":
check(3 * 4 == 12)
test "浮点比较":
check(abs(0.1 + 0.2 - 0.3) < 1e-10)
# 多个断言
suite "字符串操作":
test "长度":
let s = "Hello"
check s.len == 5
test "包含":
check "Hello World".contains("World")
check "Hello" in "Hello World"
test "拼接":
check "Hello" & " " & "World" == "Hello World"
19.1.1 断言宏
import std/unittest
suite "断言示例":
test "check 宏":
let x = 42
check x > 0
check x mod 2 == 0
test "require(失败时终止)":
let config = loadConfig()
require config != nil
test "expect(期望异常)":
expect ValueError:
discard parseInt("abc")
test "自定义失败消息":
let result = compute(10)
check result > 0
# 失败时会显示 result 的值
19.1.2 测试夹具
import std/unittest
suite "数据库测试":
setup:
# 每个测试前执行
let db = openTestDb()
teardown:
# 每个测试后执行
db.close()
test "插入记录":
db.exec("INSERT INTO users (name) VALUES (?)", "Alice")
check db.count("users") == 1
test "查询记录":
db.exec("INSERT INTO users (name) VALUES (?)", "Bob")
let name = db.getValue("SELECT name FROM users WHERE name = ?", "Bob")
check name == "Bob"
19.2 测试组织
19.2.1 项目测试结构
myproject/
├── src/
│ ├── myproject.nim
│ └── myproject/
│ ├── utils.nim
│ └── models.nim
├── tests/
│ ├── test_utils.nim
│ ├── test_models.nim
│ └── test_integration.nim
└── myproject.nimble
19.2.2 nimble 任务
# myproject.nimble
task test, "Run all tests":
exec "nim c -r tests/test_utils.nim"
exec "nim c -r tests/test_models.nim"
task test_integration, "Run integration tests":
exec "nim c -r tests/test_integration.nim"
19.2.3 测试示例
# tests/test_utils.nim
import std/unittest
import ../src/myproject/utils
suite "工具函数测试":
test "add 函数":
check add(1, 2) == 3
check add(-1, 1) == 0
check add(0, 0) == 0
test "multiply 函数":
check multiply(3, 4) == 12
check multiply(0, 100) == 0
test "较大值":
check max(5, 10) == 10
check max(10, 5) == 10
check max(5, 5) == 5
19.3 Mock 与 Stub
import std/unittest
# 使用 proc 类型实现 Mock
type
Logger = proc(msg: string)
UserService = object
logger: Logger
proc newUserService(logger: Logger): UserService =
UserService(logger: logger)
proc createUser(svc: UserService, name: string): bool =
if name.len == 0:
svc.logger("Error: empty name")
return false
svc.logger("Created user: " & name)
return true
suite "UserService 测试":
test "成功创建用户":
var logs: seq[string]
let mockLogger: Logger = proc(msg: string) =
logs.add(msg)
let svc = newUserService(mockLogger)
check svc.createUser("Alice")
check logs.len == 1
check logs[0] == "Created user: Alice"
test "空名字失败":
var logs: seq[string]
let mockLogger: Logger = proc(msg: string) =
logs.add(msg)
let svc = newUserService(mockLogger)
check not svc.createUser("")
check logs[0] == "Error: empty name"
19.4 属性测试
import std/unittest, std/random
# 简化的属性测试
template property(name: string, iterations: int = 100, body: untyped) =
test name:
for i in 0..<iterations:
body
suite "属性测试":
property "排序后长度不变", 100:
var data = newSeq[int](rand(1..100))
for i in 0..<data.len:
data[i] = rand(-1000..1000)
let originalLen = data.len
data.sort()
check data.len == originalLen
property "排序后有序", 100:
var data = newSeq[int](rand(1..100))
for i in 0..<data.len:
data[i] = rand(-1000..1000)
data.sort()
for i in 1..<data.len:
check data[i-1] <= data[i]
property "反转两次等于原序列", 100:
var data = newSeq[int](rand(1..50))
for i in 0..<data.len:
data[i] = rand(100)
let original = data
data.reverse()
data.reverse()
check data == original
19.5 集成测试
import std/unittest, std/os, std/httpclient
# 集成测试需要真实的服务运行
suite "API 集成测试":
let client = newHttpClient()
let baseUrl = "http://localhost:8080"
test "健康检查":
let response = client.getContent(baseUrl & "/health")
check response == "OK"
test "创建用户":
let payload = """{"name": "Test User", "email": "test@example.com"}"""
let response = client.postContent(baseUrl & "/api/users", payload)
let data = parseJson(response)
check data.hasKey("id")
test "获取用户列表":
let response = client.getContent(baseUrl & "/api/users")
let data = parseJson(response)
check data["total"].getInt() >= 0
19.6 nimcheck 静态分析
# 基本检查
nim check src/main.nim
# 检查整个项目
nim check --project src/main.nim
# 风格检查
nim check --hints:on --warnings:on src/main.nim
19.7 性能测试
import std/[unittest, times, strformat]
template benchmark(name: string, iterations: int, body: untyped) =
let start = cpuTime()
for i in 0..<iterations:
body
let elapsed = cpuTime() - start
echo &"{name}: {elapsed*1000:.2f} ms ({iterations} iterations)"
suite "性能基准":
test "排序性能":
benchmark "快速排序", 1000:
var data = newSeq[int](1000)
for j in 0..<data.len:
data[j] = rand(10000)
data.sort()
test "字符串拼接":
benchmark "add 方法", 100000:
var s = ""
for j in 0..99:
s.add("x")
benchmark "& 运算符", 100000:
var s = ""
for j in 0..99:
s = s & "x"
19.8 代码覆盖率
# 使用 nim 编译标志生成覆盖率信息
nim c --passC:"-fprofile-arcs -ftest-coverage" -r tests/test_utils.nim
# 生成报告
gcov tests/test_utils.nim
19.9 CI/CD 集成
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
nim-version: ['2.0.x', 'stable']
steps:
- uses: actions/checkout@v4
- uses: jiro4989/setup-nim-action@v1
with:
nim-version: ${{ matrix.nim-version }}
- name: Install dependencies
run: nimble install -d
- name: Run tests
run: nimble test
- name: Build
run: nimble build
19.10 实战示例
🏢 完整测试套件
# tests/test_calculator.nim
import std/unittest
import ../src/calculator
suite "Calculator":
setup:
var calc = newCalculator()
test "初始值为0":
check calc.result() == 0
test "加法":
calc.add(10)
calc.add(20)
check calc.result() == 30
test "减法":
calc.add(100)
calc.subtract(30)
check calc.result() == 70
test "乘法":
calc.add(5)
calc.multiply(6)
check calc.result() == 30
test "除法":
calc.add(100)
calc.divide(4)
check calc.result() == 25
test "除以零":
calc.add(10)
expect DivByZeroDefect:
calc.divide(0)
test "重置":
calc.add(100)
calc.reset()
check calc.result() == 0
test "链式操作":
check calc.add(10).multiply(2).subtract(5).result() == 15
本章小结
| 功能 | 用途 | 命令 |
|---|---|---|
unittest | 单元测试 | nim c -r tests/test.nim |
check | 断言 | check condition |
require | 必须通过 | require condition |
expect | 期望异常 | expect ExceptionType: |
suite | 测试套件 | suite "name": |
nimcheck | 静态分析 | nim check file.nim |
练习
- 为之前编写的模块编写完整的单元测试
- 使用属性测试验证排序算法的正确性
- 设置 GitHub Actions 自动化测试
扩展阅读
← 上一章:Web 开发 | 下一章:容器化部署 →