Встроенное тестирование

Пишем тесты прямо в исходном файле и запускаем их встроенной командой.

Блок test — особая конструкция Zig, которая содержит проверочный код прямо в исходном файле; команда zig test находит все такие блоки и выполняет их.

Во многих языках тесты — это отдельный фреймворк, который надо подключать. В Zig тестирование встроено в язык: вы пишете блок test прямо рядом с кодом, который он проверяет, и запускаете zig test. Никаких зависимостей, никакой настройки.

Блок test рядом с кодом

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

fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "сложение работает" {
    try expect(add(2, 3) == 5);
    try expect(add(-1, 1) == 0);
}

Блок test "имя" содержит проверки. Функция std.testing.expect принимает bool и возвращает ошибку, если условие ложно — поэтому перед ней пишут try. Имя теста — обычная строка, удобная для отчёта. Тесты живут в том же файле, что и код: их легко держать в синхроне.

Запуск тестов

zig test mymodule.zig
# All 1 tests passed.

Команда zig test компилирует файл в специальный тестовый бинарник, находит все блоки test и выполняет их, выводя результат. В обычной сборке (build-exe) тестовые блоки игнорируются — они не попадают в продакшен-бинарник.

Сравнение значений и ошибок

test "проверка равенства и ошибок" {
    try std.testing.expectEqual(@as(i32, 10), add(7, 3));
    // ожидаем, что функция вернёт конкретную ошибку:
    try std.testing.expectError(error.Empty, parse(""));
}

expectEqual сравнивает два значения и при несовпадении печатает оба — удобнее голого expect. expectError проверяет, что выражение вернуло именно ожидаемую ошибку. Это покрывает и успешные пути, и обработку ошибок.

Тестовый аллокатор ловит утечки

test "нет утечек памяти" {
    const alloc = std.testing.allocator; // специальный аллокатор для тестов
    const buf = try alloc.alloc(u8, 8);
    defer alloc.free(buf); // забудь defer — тест упадёт с сообщением об утечке
}

В тестах доступен std.testing.allocator — аллокатор, который автоматически проверяет на утечки. Если в тесте выделили память и не освободили, тест провалится с указанием утечки. Это превращает тесты ещё и в проверку корректности управления памятью — мощная связка с философией явных аллокаторов.

Как работает под капотом

При zig test компилятор собирает отдельный исполняемый файл, в котором каждый блок test становится функцией, а сгенерированная точка входа по очереди их вызывает. Поскольку тесты — часть языка, а не библиотеки, они имеют доступ к приватным функциям модуля и компилируются в том же контексте, что и код. std.testing.allocator — это обёртка над отладочным аллокатором с проверкой утечек на выходе.

Частые ошибки

Первая — забыть try перед expect: без него ошибка теста не сработает. Вторая — использовать обычный аллокатор вместо std.testing.allocator и упустить проверку утечек. Третья — ждать, что тесты попадут в обычный бинарник: они выполняются только через zig test.

Итог

  • Тесты пишут в блоках test "имя" { ... } прямо в исходном файле.
  • zig test file.zig находит и выполняет все тесты; в обычной сборке они игнорируются.
  • expect, expectEqual, expectError проверяют условия, значения и ошибки.
  • std.testing.allocator ловит утечки памяти прямо в тестах.
Проверьте себя
1. Где располагаются тесты в Zig?
AВ отдельном фреймворке, который нужно подключить
BВ блоках test прямо в исходном файле рядом с кодом
CТолько в каталоге tests/
DВ комментариях
2. Что особенного даёт std.testing.allocator в тестах?
AОн быстрее обычного
BОн автоматически проверяет на утечки и проваливает тест при неосвобождённой памяти
CОн не требует free
DОн работает без кучи