Skip to content

Zig 测试框架完全教程

const std = @import("std");
// 命名测试(字符串字面量)
test "加法测试" {
try std.testing.expect(add(2, 3) == 5);
}
// 文档测试(标识符)
test add {
// 这个测试同时作为 add 函数的文档示例
try std.testing.expect(add(41, 1) == 42);
}
/// 两个数相加
fn add(a: i32, b: i32) i32 {
return a + b;
}
Terminal window
# 运行所有测试
zig test math.zig
# 输出示例:
# 1/2 测试.加法测试...OK
# 2/2 测试.decltest.add...OK
# 所有 2 个测试通过
  • 隐式返回类型: anyerror!void
  • 顺序无关: 可以放在任何位置
  • 仅测试构建: 只有 zig test 时才包含
  • 可放在单独文件: 不需要和被测代码放一起
/// 计算斐波那契数
/// 示例:
/// ```zig
/// const fib = fibonacci(5);
/// try expect(fib == 5);
/// ```
pub fn fibonacci(n: u32) u32 {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 文档测试(自动关联到 fibonacci 函数)
test fibonacci {
try std.testing.expect(fibonacci(0) == 0);
try std.testing.expect(fibonacci(1) == 1);
try std.testing.expect(fibonacci(5) == 5);
try std.testing.expect(fibonacci(10) == 55);
}
test "失败的测试" {
// 测试失败会显示错误堆栈跟踪
try std.testing.expect(1 + 1 == 3);
}
test "成功的测试" {
try std.testing.expect(1 + 1 == 2);
}

输出:

1/2 测试.失败的测试...FAIL (TestUnexpectedResult)
测试文件.zig:3:5: 0x102f078 in 测试.失败的测试
try std.testing.expect(1 + 1 == 3);
^
2/2 测试.成功的测试...OK
1 个通过; 0 个跳过; 1 个失败.
test "可能失败的测试" {
const result = mightFail();
if (result) |value| {
try std.testing.expect(value == 42);
} else |err| {
// 测试中处理错误
std.debug.print("测试失败: {}\n", .{err});
return err;
}
}
fn mightFail() !i32 {
return error.NotImplemented;
}
Terminal window
# 只运行包含"math"的测试
zig test my_tests.zig --test-filter math
# 只运行特定测试
zig test my_tests.zig --test-filter "测试加法"
test "需要特定环境的测试" {
// 检查运行环境
if (!hasRequiredFeature()) {
std.debug.print("跳过: 需要特定功能\n", .{});
return error.SkipZigTest;
}
try std.testing.expect(doFeatureTest() == true);
}
test "总是跳过" {
// 明确跳过
return error.SkipZigTest;
}
const std = @import("std");
test "检测内存泄漏" {
const allocator = std.testing.allocator;
// 正确: 使用 defer 确保释放
var list1 = std.ArrayList(u8).init(allocator);
defer list1.deinit(); // ✅ 确保释放
try list1.appendSlice("hello");
// 错误: 忘记 deinit (会被检测到)
var list2 = std.ArrayList(u8).init(allocator);
// 忘记: defer list2.deinit();
try list2.appendSlice("world");
try std.testing.expect(list1.items.len == 5);
}
1/1 测试.检测内存泄漏...OK
[gpa] (err): 内存地址 0x7f74a8aa0000 泄漏:
文件路径:行号:列号: 0x10aa8fe in 函数名
const new_memory = try self.allocator.alignedAlloc(T, alignment, new_capacity);
^
...
所有 1 个测试通过.
1 个错误被记录.
1 个测试泄漏内存.
const std = @import("std");
const builtin = @import("builtin");
test "只在测试中运行" {
// 使用 @import("builtin").is_test 检测
if (builtin.is_test) {
// 测试专用代码
try testSpecificLogic();
}
}
// 条件编译测试代码
fn debugHelper() void {
// 只在测试构建中编译
comptime {
if (!builtin.is_test) {
@compileError("此函数只能在测试中使用");
}
}
// 调试代码...
}
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
const expectEqualStrings = std.testing.expectEqualStrings;
test "各种断言" {
// 基本断言
try expect(true);
try expect(1 + 1 == 2);
// 相等断言(自动类型转换)
const expected: i32 = 42;
const actual: i64 = 42;
try expectEqual(expected, actual); // i64 转换为 i32
// 字符串相等
try expectEqualStrings("hello", "hello");
// 错误断言
const result: anyerror!void = error.SomeError;
try expectError(error.SomeError, result);
// 可选类型断言
const maybe_value: ?i32 = 42;
try expect(maybe_value != null);
try expectEqual(42, maybe_value.?);
// 浮点数比较(带误差)
const a: f64 = 0.1 + 0.2;
const b: f64 = 0.3;
try expect(std.math.approxEqAbs(f64, a, b, 0.0000001));
}
test "切片比较" {
const expected = [_]i32{1, 2, 3, 4, 5};
const actual = [_]i32{1, 2, 3, 4, 5};
try std.testing.expectEqualSlices(i32, &expected, &actual);
// 也可以直接比较
try expect(std.mem.eql(i32, &expected, &actual));
}
test "字符串比较" {
const str1 = "hello";
const str2 = "hello";
try std.testing.expectEqualStrings(str1, str2);
// 或
try expect(std.mem.eql(u8, str1, str2));
}
const TestContext = struct {
allocator: std.mem.Allocator,
temp_buffer: [1024]u8,
pub fn init() TestContext {
return .{
.allocator = std.testing.allocator,
.temp_buffer = undefined,
};
}
pub fn createString(self: *TestContext, text: []const u8) ![]u8 {
return try self.allocator.dupe(u8, text);
}
};
test "使用测试夹具" {
var ctx = TestContext.init();
defer {
// 清理资源...
}
const str = try ctx.createString("test");
defer ctx.allocator.free(str);
try expectEqualStrings("test", str);
}
const TestCase = struct {
input: i32,
expected: i32,
};
test "参数化测试示例" {
const cases = [_]TestCase{
.{ .input = 0, .expected = 0 },
.{ .input = 1, .expected = 1 },
.{ .input = 5, .expected = 5 },
.{ .input = 10, .expected = 55 },
};
for (cases) |case| {
const actual = fibonacci(case.input);
try expectEqual(case.expected, actual);
}
}
// 定义接口
const Database = struct {
const Self = @This();
query: *const fn (self: *Self, sql: []const u8) ![]const u8,
pub fn init() Self {
return .{
.query = defaultQuery,
};
}
fn defaultQuery(self: *Self, sql: []const u8) ![]const u8 {
_ = self;
_ = sql;
return error.NotImplemented;
}
};
test "使用模拟数据库" {
var mock_db = Database.init();
// 替换为模拟实现
mock_db.query = struct {
fn mockQuery(_: *Database, sql: []const u8) ![]const u8 {
if (std.mem.eql(u8, sql, "SELECT * FROM users")) {
return "模拟数据";
}
return error.QueryFailed;
}
}.mockQuery;
const result = try mock_db.query("SELECT * FROM users");
try expectEqualStrings("模拟数据", result);
}
// math.zig - 被测代码
pub fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn multiply(a: i32, b: i32) i32 {
return a * b;
}
// tests/math_test.zig - 测试文件
const std = @import("std");
const math = @import("../math.zig");
test "加法测试套件" {
// 分组相关测试
try std.testing.expect(math.add(0, 0) == 0);
try std.testing.expect(math.add(1, 2) == 3);
try std.testing.expect(math.add(-1, 1) == 0);
}
test "乘法测试套件" {
try std.testing.expect(math.multiply(0, 5) == 0);
try std.testing.expect(math.multiply(3, 4) == 12);
try std.testing.expect(math.multiply(-2, 3) == -6);
}
build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "运行所有测试");
// 单元测试
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = b.graph.host,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
test_step.dependOn(&run_unit_tests.step);
// 集成测试
const integration_tests = b.addTest(.{
.root_source_file = b.path("tests/integration.zig"),
.target = b.graph.host,
});
const run_integration_tests = b.addRunArtifact(integration_tests);
test_step.dependOn(&run_integration_tests.step);
// 跨平台测试
const cross_targets = [_]std.Target.Query{
.{ .cpu_arch = .x86_64, .os_tag = .linux },
.{ .cpu_arch = .x86_64, .os_tag = .windows },
.{ .cpu_arch = .aarch64, .os_tag = .macos },
};
for (cross_targets) |target| {
const cross_test = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = b.resolveTargetQuery(target),
});
const run_cross_test = b.addRunArtifact(cross_test);
run_cross_test.skip_foreign_checks = true;
test_step.dependOn(&run_cross_test.step);
}
}
// 好的测试命名
test "user_creation_succeeds_with_valid_data" {...}
test "database_connection_fails_when_offline" {...}
test "api_returns_404_for_nonexistent_resource" {...}
// 使用 Given-When-Then 模式
test "given_valid_credentials_when_login_then_returns_token" {
// Given: 准备测试数据
const credentials = Credentials{ .username = "alice", .password = "secret" };
// When: 执行操作
const result = try login(credentials);
// Then: 验证结果
try expect(result.token.len > 0);
try expect(result.expires_at > std.time.timestamp());
}
// 每个测试独立运行,不共享状态
test "独立测试 1" {
var state = State.init(std.testing.allocator);
defer state.deinit();
// 修改状态
try state.addItem("test1");
try expect(state.count() == 1);
}
test "独立测试 2" {
// 重新创建状态,不受前一个测试影响
var state = State.init(std.testing.allocator);
defer state.deinit();
try expect(state.count() == 0); // 总是从干净状态开始
}
test "性能基准" {
const iterations = 1000000;
const start = std.time.nanoTimestamp();
var sum: i64 = 0;
for (0..iterations) |i| {
sum += @as(i64, i);
}
const end = std.time.nanoTimestamp();
const elapsed_ns = end - start;
const elapsed_ms = @as(f64, @floatFromInt(elapsed_ns)) / 1_000_000.0;
std.debug.print("执行 {d} 次循环耗时: {d:.2} ms\n", .{ iterations, elapsed_ms });
try expect(sum > 0); // 确保循环实际执行
}
Terminal window
# 查看所有测试选项
zig test --help
# 常用选项:
# 过滤测试
zig test --test-filter "math"
# 设置随机种子(用于重现随机失败)
zig test --seed 0x12345678
# 详细输出
zig test --verbose
# 并行运行测试(默认)
zig test -j 4
# 不并行运行
zig test --single-threaded
# 内存限制
zig test --maxrss 1024M
# 跳过内存不足的测试
zig test --skip-oom-steps
# 监视模式(文件变化时重新运行)
zig test --watch
# 生成覆盖率报告(需要编译标志)
zig test -fcoverage
# 测试报告格式
zig test --summary all # 完整报告
zig test --summary failures # 只显示失败(默认)
zig test --summary none # 不显示报告
math.zig
const std = @import("std");
pub fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn subtract(a: i32, b: i32) i32 {
return a - b;
}
pub fn multiply(a: i32, b: i32) i32 {
return a * b;
}
pub fn divide(a: i32, b: i32) !i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
// 文档测试
test add {
try std.testing.expect(add(2, 3) == 5);
try std.testing.expect(add(-1, 1) == 0);
try std.testing.expect(add(0, 0) == 0);
}
test subtract {
try std.testing.expect(subtract(5, 3) == 2);
try std.testing.expect(subtract(0, 5) == -5);
}
test multiply {
try std.testing.expect(multiply(3, 4) == 12);
try std.testing.expect(multiply(0, 100) == 0);
try std.testing.expect(multiply(-2, 3) == -6);
}
test "除法测试" {
// 正常除法
try std.testing.expectEqual(5, try divide(10, 2));
try std.testing.expectEqual(-3, try divide(9, -3));
// 除零错误
try std.testing.expectError(error.DivisionByZero, divide(10, 0));
}
test "边界情况" {
const max = std.math.maxInt(i32);
const min = std.math.minInt(i32);
// 溢出检测(如果 add 会溢出,需要错误处理)
// try std.testing.expectError(error.Overflow, safeAdd(max, 1));
// 边界值测试
try std.testing.expect(subtract(max, max) == 0);
try std.testing.expect(multiply(1, min) == min);
}
test "内存安全" {
const allocator = std.testing.allocator;
// 确保没有内存泄漏
var numbers = std.ArrayList(i32).init(allocator);
defer numbers.deinit();
for (0..100) |i| {
try numbers.append(@intCast(i));
}
try std.testing.expect(numbers.items.len == 100);
}

Zig 测试框架的关键特性:

  1. 简单直观: 只需 test 关键字和断言
  2. 文档集成: 文档测试自动关联和验证
  3. 内存安全: 自动检测内存泄漏
  4. 灵活控制: 可跳过、过滤、参数化测试
  5. 丰富断言: 多种专用断言函数
  6. 构建集成: 可与构建系统深度集成

通过遵循最佳实践,你可以创建可靠、可维护的测试套件,确保代码质量并防止回归错误。