🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Test Organization

Test organization is how tests are grouped, so a reader can find the tests for one topic in one place. Grouping by topic keeps a growing set of tests easy to read and easy to extend.

Names that show the topic

A topic prefix at the start of a test name groups related tests. Readers can see the topic before reading the rest of the name.

const std = @import("std");
fn add(a: i32, b: i32) i32 {
    return a + b;
}
fn double(n: i32) i32 {
    return n * 2;
}
test "add: sums two numbers" {
    try std.testing.expectEqual(@as(i32, 5), add(2, 3));
}
test "add: adds zero" {
    try std.testing.expectEqual(@as(i32, 4), add(4, 0));
}
test "double: doubles four" {
    try std.testing.expectEqual(@as(i32, 8), double(4));
}

A string group

String tests form their own group with their own prefix. The string prefix marks all three tests here.

const std = @import("std");
fn greeting() []const u8 {
    return "hello, zig";
}
fn farewell() []const u8 {
    return "goodbye, zig";
}
fn welcome() []const u8 {
    return "welcome";
}
test "string: greeting matches" {
    try std.testing.expectEqualStrings("hello, zig", greeting());
}
test "string: farewell matches" {
    try std.testing.expectEqualStrings("goodbye, zig", farewell());
}
test "string: welcome matches" {
    try std.testing.expectEqualStrings("welcome", welcome());
}

An error group

Error tests group by topic too. The check prefix marks the error tests and the success test together.

const std = @import("std");
const CheckError = error{ TooSmall, TooBig };
fn check(n: i32) CheckError!i32 {
    if (n < 0) return error.TooSmall;
    if (n > 10) return error.TooBig;
    return n * 2;
}
test "check: rejects a negative number" {
    try std.testing.expectError(error.TooSmall, check(-1));
}
test "check: rejects a large number" {
    try std.testing.expectError(error.TooBig, check(99));
}
test "check: doubles a good number" {
    try std.testing.expectEqual(@as(i32, 6), try check(3));
}

Example

const std = @import("std");
fn add(a: i32, b: i32) i32 {
    return a + b;
}
fn double(n: i32) i32 {
    return n * 2;
}
fn greeting() []const u8 {
    return "hello, zig";
}
fn farewell() []const u8 {
    return "goodbye, zig";
}
test "math: adds two numbers" {
    try std.testing.expectEqual(@as(i32, 5), add(2, 3));
}
test "math: doubles three" {
    try std.testing.expectEqual(@as(i32, 6), double(3));
}
test "string: greeting matches" {
    try std.testing.expectEqualStrings("hello, zig", greeting());
}
test "string: farewell matches" {
    try std.testing.expectEqualStrings("goodbye, zig", farewell());
}

Key points

  • Test file: a test file is a file that holds the tests for one topic, such as math or strings.
  • Group tests by topic, so the tests for one function sit together.
  • Name each test with its topic first, so related tests read together.
  • Keep one idea per test block, even inside a grouped set, so a failure still points at one check.
  • A test helper can serve one group, while a helper used by many groups earns a shared home.
  • When a group grows large, split it into smaller groups by topic instead of letting one group sprawl.

This run shows two groups in one place, math and string tests side by side. In a larger project each group would live in its own test file for that topic. The prefixes keep the groups visible even when the tests share a run.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What is the best way to organize the tests for one topic?