Ziglings 笔记 102: 第一道防线 (Built-in Testing)

代码的守护者

在 Ziglings 的第 102 个练习中,我们接触到了 Zig 哲学中非常重要的一部分:测试即文档

在其他语言中,测试通常住在单独的 tests/ 目录下,与源码分离。但在 Zig 中,你被鼓励将测试直接写在源码文件里。这样做有两个巨大的好处:

  1. 文档化:其他开发者看到函数定义后,往下一瞄就能看到它是如何被使用的。
  2. 可维护性:当你修改函数时,测试就在旁边,你很难忘记更新它。

挑战:编写断言

我们需要为一个简单的数学库编写测试用例。 特别是 sub(减法)和 divide(除法)函数,需要验证它们的正确性以及错误处理能力。

解决方案

这是包含完整测试用例的代码:

const std = @import("std");
const testing = std.testing;

// 被测函数 1: 加法
fn add(a: f16, b: f16) f16 {
    return a + b;
}

test "add" {
    // 方式 1: 基础断言
    try testing.expect(add(41, 1) == 42);

    // 方式 2: 相等断言 (推荐)
    // 参数顺序:(期望值, 实际值)
    try testing.expectEqual(42, add(41, 1));

    try testing.expect(add(5, -4) == 1);
    try testing.expect(add(1.5, 1.5) == 3);
}

// 被测函数 2: 减法
fn sub(a: f16, b: f16) f16 {
    return a - b;
}

test "sub" {
    // 验证减法逻辑
    try testing.expect(sub(10, 5) == 5);
    try testing.expect(sub(3, 1.5) == 1.5);
}

// 被测函数 3: 除法 (可能返回错误)
fn divide(a: f16, b: f16) !f16 {
    if (b == 0) return error.DivisionByZero;
    return a / b;
}

test "divide" {
    // 测试正常情况
    // catch unreachable 表示:这里绝不应该报错,如果报错了,测试直接 Panic 失败
    try testing.expect(divide(2, 2) catch unreachable == 1);
    try testing.expect(divide(10, 2) catch unreachable == 5);

    // 测试错误情况
    // 核心工具: expectError
    // 验证 divide(15, 0) 是否真的返回了 DivisionByZero 错误
    try testing.expectError(error.DivisionByZero, divide(15, 0));
}

核心知识点总结

1. zig test 命令

要运行这些测试,只需在终端输入:

zig test main.zig

如果一切正常,它会显示 “All 3 tests passed.”。如果失败,它会精确地指出是哪一行代码、期望是什么、实际得到了什么。

2. 测试也是代码

test 块内部就是普通的 Zig 代码。 这意味着你可以在测试里定义变量、结构体,甚至辅助函数。 特别是在测试泛型代码时,你经常会在测试块内部定义一些临时的结构体来模拟各种场景。

3. TDD (测试驱动开发) 的利器

Zig 的这种设计让 TDD 变得极其顺滑。 写一个函数 -> 在下面写一个 test -> 运行 zig test -> 循环。 不需要配置复杂的 CMake 或 Makefile,开箱即用。


后续预告:我们的 Ziglings 基础语法之旅已经全部完成!🎉 从 Hello World 到指针,从 Comptime 元编程到标准库测试。你现在已经是一名合格的 Zig 开发者了。接下来的路,建议通过构建实际项目(如 Web 服务器、游戏引擎、系统工具)来继续精进。愿构建速度与你同在!