# Data driven / Table driven testing

**URL:** <https://ziggit.dev/t/data-driven-table-driven-testing/17013>\
**Category:** Help\
**Tags:** language\
**Created:** [July 31, 2026, 10:01am UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013 "2026-07-31T10:01:17Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![Kaeru](https://ziggit.dev/user_avatar/ziggit.dev/kaeru/32/3335_2.png) [@Kaeru](https://ziggit.dev/u/Kaeru)\
**Post date:** [July 31, 2026, 10:01am UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/1 "2026-07-31T10:01:17Z")

</div>

I’ve been a very big fan of data driven testing / table driven testing, however I don’t feel like this pattern is very well supported by Zig, so I wanted to know how other people approach this problem.

As far as I know the Zig standard library does not employ these testing techniques for example in `ldexp.zig` (Codeberg is down so copying the code here).

```zig
test ldexp {
    // subnormals
    try expect(ldexp(@as(f16, 0x1.1FFp14), -14 - 9 - 15) == math.floatTrueMin(f16));
    try expect(ldexp(@as(f32, 0x1.3FFFFFp-1), -126 - 22) == math.floatTrueMin(f32));
    try expect(ldexp(@as(f64, 0x1.7FFFFFFFFFFFFp-1), -1022 - 51) == math.floatTrueMin(f64));
    try expect(ldexp(@as(f80, 0x1.7FFFFFFFFFFFFFFEp-1), -16382 - 62) == math.floatTrueMin(f80));
    try expect(ldexp(@as(f128, 0x1.7FFFFFFFFFFFFFFFFFFFFFFFFFFFp-1), -16382 - 111) == math.floatTrueMin(f128));

    try expect(ldexp(math.floatMax(f32), -128 - 149) > 0.0);
    try expect(ldexp(math.floatMax(f32), -128 - 149 - 1) == 0.0);
}

```

The big advantage of data driven testing for me is that the test does not “fail fast” instead each failure will be shown individually.

Go handles this in a nice manner by using subtests [aoc/go/cmd/02/02\_test.go at master · nunokaeru/aoc · GitHub](https://github.com/nunokaeru/aoc/blob/master/go/cmd/02/02_test.go)

In C and C++ you can use macros to create different test cases, for example in CATCH a test could look like:

```cpp
TEST_CASE("int")
{
    auto [a, b] = GENERATE(table<int, int>({
        {0,0},
        {1,1},
        {2,2},
    }));
    REQUIRE(a == b);
}

```

I’ve found a way to (ab)use comptime to create these types of tests in Zig but I am not super happy with the approach. Here’s an example:

```zig
test "not duplicated" {
    comptime {
        const values = [_]u64{
            12,
            13,
            12312,
            41141,
            1992191921992,
        };
        for (values) |n| {
            _ = NotDuplicatedTest(n);
        }
    }
}

fn NotDuplicatedTest(
    comptime number: u64,
) type {
    return struct {
        test {
            var intBuffer: [20]u8 = undefined;
            try std.testing.expect(!duplicates(intBuffer[0..], number));
        }
    };
}

```

Has anyone tried to approach this topic and has a better solution for this?

---

<div class="post-metadata">

**Author:** ![filo](https://ziggit.dev/user_avatar/ziggit.dev/filo/32/7401_2.png) [@filo](https://ziggit.dev/u/filo)\
**Post date:** [July 31, 2026, 10:45am UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/2 "2026-07-31T10:45:22Z")

</div>

I’m doing something similar [here](https://codeberg.org/intales/giacomo/src/commit/e0136766550022fd8697efc045ebb1675f6c58f6/modbuz/src/pdu/pdu_test.zig#L64).

It is a bit verbose, but it gets the job done; not sure if this helps you

---

<div class="post-metadata">

**Author:** ![Kaeru](https://ziggit.dev/user_avatar/ziggit.dev/kaeru/32/3335_2.png) [@Kaeru](https://ziggit.dev/u/Kaeru)\
**Post date:** [July 31, 2026, 10:55am UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/3 "2026-07-31T10:55:25Z")

</div>

That looks very similar to the Go approach but it “fails fast” right, as in If the first case fails the rest of the cases won’t be executed

---

<div class="post-metadata">

**Author:** ![filo](https://ziggit.dev/user_avatar/ziggit.dev/filo/32/7401_2.png) [@filo](https://ziggit.dev/u/filo)\
**Post date:** [July 31, 2026, 11:08am UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/4 "2026-07-31T11:08:07Z")

</div>

Yep, exactly.  
It’s not the best approach but works good enough for my use case.  
Maybe you could do something with `std.Io` to make them parallel via `std.testing.io`?

I’m just throwing ideas 🙂

---

<div class="post-metadata">

**Author:** ![pzittlau](https://ziggit.dev/letter_avatar_proxy/v4/letter/p/ecae2f/32.png) [@pzittlau](https://ziggit.dev/u/pzittlau)\
**Post date:** [July 31, 2026, 11:34am UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/5 "2026-07-31T11:34:18Z")

</div>

This is a pattern I like to do:

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

test {
    const as = [_]u32{ 1, 2, 5, 8, 1234 };
    const bs = [_]u32{ 1, 2, 5, 8, 1234 };

    for (as) |a| {
        for (bs) |b| {
            try oneTest(a, b);
        }
    }
}

fn oneTest(a: u32, b: u32) !void {
    errdefer std.log.err("{} {}", .{ a, b });
    try testing.expect(a * b < std.math.maxInt(u16));
}

```

Note the `errdefer` in the test function.

---

<div class="post-metadata">

**Author:** ![filo](https://ziggit.dev/user_avatar/ziggit.dev/filo/32/7401_2.png) [@filo](https://ziggit.dev/u/filo)\
**Post date:** [July 31, 2026, 12:00pm UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/6 "2026-07-31T12:00:40Z")

</div>

Cool! I should try this

---

<div class="post-metadata">

**Author:** ![matklad](https://ziggit.dev/user_avatar/ziggit.dev/matklad/32/2462_2.png) [@matklad](https://ziggit.dev/u/matklad)\
**Post date:** [July 31, 2026, 12:35pm UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/8 "2026-07-31T12:35:20Z")

</div>

What I’ve learned from andrew, and what I also agree with, is that this:

```zig
// BAD
test {
    inline for(.{
        .{a1, b1, c1},
        .{a2, b2, c2},
        .{a3, b3, c3},
    }) |test_case| {
        const a, const b, const c = test_case;
        // code
    }
}

```

is worse than this:

```zig
// GOOD
fn check(a: A, b: B, c: C) !void {
    // code
}

test {
    try check(a1, b1, c1);
    try check(a2, b2, c2);
    try check(a3, b3, c3);
}

```

The function call version is shorter, and gives significantly more useful stack trace on failure, that points at the specific case (one might recall Dijkstra idea from the famous paper about adding loop counters to stack traces).

Both options are fail-fast, but I consider that to be a feature: you can order the cases from the simplest to hardest one, to create “suggested debugging order”.

With two and more-dimensional data, it makes sense to go with for loops like pzittlau suggests, to get O(N\*M) tests out of O(N+M) source code.

The for loop can be improved by adding

```zig
errdefer log.err("case={}", .{test_case});

```

to its header, to print specific failing tests on an explicit failure, but that’s not universally useful, as, ideally, incorrect behavior is caught by `std.debug.assert` even before you get to `try expect` the wrong answer.

---

<div class="post-metadata">

**Author:** ![Kaeru](https://ziggit.dev/user_avatar/ziggit.dev/kaeru/32/3335_2.png) [@Kaeru](https://ziggit.dev/u/Kaeru)\
**Post date:** [July 31, 2026, 1:03pm UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/9 "2026-07-31T13:03:37Z")

</div>

I think the non fail fast property when designing a set of tests is quite useful. Usually I find that the information obtained from how many and which X out of Y tests fail to help significantly in finding what the issue is.

I haven’t found it useful to use inline or comprime for sequential tests. Besides the “not fail fast” the examples provided also have three other benefits (at least the go and cpp examples, not entirely sure about the semantics of the zig example):

- It’s guaranteed that each test cannot affect another (unless it touches global state).
- The execution order of each test is _not_ guaranteed
- These tests can be concurrent, it’s up to the test executor

---

<div class="post-metadata">

**Author:** ![dennypenta](https://ziggit.dev/user_avatar/ziggit.dev/dennypenta/32/8587_2.png) [@dennypenta](https://ziggit.dev/u/dennypenta)\
**Post date:** [July 31, 2026, 1:50pm UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/10 "2026-07-31T13:50:36Z")

</div>

stacktracy point really is a winner.

either using function like style or put explicit error log with a ficitonal test name

---

<div class="post-metadata">

**Author:** ![Sze](https://ziggit.dev/user_avatar/ziggit.dev/sze/32/496_2.png) [@Sze](https://ziggit.dev/u/Sze)\
**Post date:** [July 31, 2026, 4:04pm UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/11 "2026-07-31T16:04:49Z")

</div>

related:

> [@Comptime test names](https://ziggit.dev/t/comptime-test-names/13503):
>
> I am a new to Zig and was intrigued by the idea of what comptime could do for generating code. The first idea I wanted to try was using comptime for loops to generate a bunch of orthogonal test cases. That didn’t quite work as expected (compile error: “expected statement, found test”) until I found this forum solution: [Generating Tests at Comptime - #4 by koe](https://ziggit.dev/t/generating-tests-at-comptime/6473/4) But, the solution does not provide a way to name the test, so this really isn’t a full solution (I want to test a subset of tests often…

---

<div class="post-metadata">

**Author:** ![vancluever](https://ziggit.dev/user_avatar/ziggit.dev/vancluever/32/4911_2.png) [@vancluever](https://ziggit.dev/u/vancluever)\
**Post date:** [August 1, 2026, 3:30am UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/12 "2026-08-01T03:30:56Z")

</div>

This is mine that I have in z2d:

```zig
/// Internal table test helper. Passes in an array of structs representing test
/// cases. "name" is the only expected field, otherwise it's entirely handled
/// by `f`.
pub fn runCases(
    name: []const u8,
    cases: anytype,
    comptime f: *const fn (case: anytype) TestingError!void,
) !void {
    for (cases) |tc| {
        f(tc) catch |err| {
            debug.print("FAIL: {s}/{s}\n", .{ name, tc.name });
            return err;
        };
    }
}

```

It kind of has some duplication because you have to assign your tests a name for reporting (maybe one day we’ll be able to have access to the name of a running test inside of it), but you could easily discard that part if you don’t need it.

Then from there you can just do the standard table-based tests pattern where you create a `cases` table and pass it in.

You could probably adjust it fairly easy too to take a filter that could be passed in as a build option.

Fail fast issues could also be easily addressed by collecting failing tests in the loop and reporting on them after.

---

<div class="post-metadata">

**Author:** ![pzittlau](https://ziggit.dev/letter_avatar_proxy/v4/letter/p/ecae2f/32.png) [@pzittlau](https://ziggit.dev/u/pzittlau)\
**Post date:** [August 1, 2026, 4:29am UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/13 "2026-08-01T04:29:06Z")

</div>

> [@vancluever](#):
>
> maybe one day we’ll be able to have access to the name of a running test inside of it

Since a `test` is just a function you can inspect the debuginfo for it. I’ve done this once in 0.15.1 for a benchmarking library I did:

```zig
fn getTestName() !?[]const u8 {
    // HACK: get name of test we run in, if any
    const debug_info = try std.debug.getSelfDebugInfo();
    var it = std.debug.StackIterator.init(null, null);
    while (it.next()) |address| {
        const module = try debug_info.getModuleForAddress(address);
        const symbol_info = try module.getSymbolAtAddress(debug_info.allocator, address);
        if (mem.startsWith(u8, symbol_info.name, "test.")) {
            return symbol_info.name[5..];
        }
    }
    return null;
}

```

I don’t know if that still works or if any of the functions used still exist. Also of course it’s a bit hacky and doesn’t work if the binary is stripped.

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [August 7, 2026, 10:18pm UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/14 "2026-08-07T22:18:49Z")

</div>

> [@Kaeru](#):
>
> I’ve been a very big fan of data driven testing / table driven testing

well, “data driven testing” might work, when that data format is fixed forever.  
but that’s impossible, there are all those `md`, `rst` etc.

but “format” is just a “form”, mind “content”… 🙂

---

<div class="post-metadata">

**Author:** ![filo](https://ziggit.dev/user_avatar/ziggit.dev/filo/32/7401_2.png) [@filo](https://ziggit.dev/u/filo)\
**Post date:** [August 8, 2026, 7:21am UTC](https://ziggit.dev/t/data-driven-table-driven-testing/17013/15 "2026-08-08T07:21:22Z")

</div>

Interesting!  
This looks better than how I usually do this kind of tests, I need to try this.

Thanks for sharing 🙂
