Cheatsheet: Testing em Zig

Cheatsheet: Testing em Zig

Zig tem suporte nativo a testes integrado diretamente na linguagem. Não é necessário nenhum framework externo — você escreve blocos test no mesmo arquivo do código e o compilador cuida do resto. Isso incentiva a prática de testes desde o primeiro momento.

Teste Básico

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

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

test "somar dois números positivos" {
    try expect(somar(2, 3) == 5);
}

test "somar com zero" {
    try expect(somar(0, 42) == 42);
}

test "somar números negativos" {
    try expect(somar(-1, -1) == -2);
}

Executar:

zig test arquivo.zig

Funções de Asserção

expect — Verificação booleana

const expect = std.testing.expect;

test "expect básico" {
    try expect(true);
    try expect(1 + 1 == 2);
    try expect("abc".len == 3);
}

expectEqual — Comparação de igualdade

const expectEqual = std.testing.expectEqual;

test "expectEqual" {
    try expectEqual(@as(i32, 42), somar(40, 2));
    try expectEqual(@as(usize, 3), "abc".len);
}

expectEqualStrings — Comparação de strings

const expectEqualStrings = std.testing.expectEqualStrings;

test "comparar strings" {
    const resultado = "Olá, mundo!";
    try expectEqualStrings("Olá, mundo!", resultado);
}

expectEqualSlices — Comparação de slices

const expectEqualSlices = std.testing.expectEqualSlices;

test "comparar slices" {
    const a = [_]i32{ 1, 2, 3 };
    const b = [_]i32{ 1, 2, 3 };
    try expectEqualSlices(i32, &a, &b);
}

expectApproxEqAbs — Comparação de floats

const expectApproxEqAbs = std.testing.expectApproxEqAbs;

test "comparar floats com tolerância" {
    try expectApproxEqAbs(@as(f64, 3.14159), 3.14160, 0.001);
}

expectError — Verificar que retorna erro

const expectError = std.testing.expectError;

fn dividir(a: i32, b: i32) !i32 {
    if (b == 0) return error.DivisaoPorZero;
    return @divTrunc(a, b);
}

test "divisão por zero retorna erro" {
    try expectError(error.DivisaoPorZero, dividir(10, 0));
}

Tabela de Asserções

FunçãoDescriçãoExemplo
expect(bool)Verifica condição booleanatry expect(x > 0)
expectEqual(esperado, obtido)Igualdade exatatry expectEqual(42, f())
expectEqualStrings(a, b)Igualdade de stringstry expectEqualStrings("ab", s)
expectEqualSlices(T, a, b)Igualdade de slicestry expectEqualSlices(u8, &a, &b)
expectApproxEqAbs(a, b, tol)Float com tolerância absolutatry expectApproxEqAbs(3.14, pi, 0.01)
expectApproxEqRel(a, b, tol)Float com tolerância relativatry expectApproxEqRel(100.0, x, 0.01)
expectError(err, expr)Verifica erro específicotry expectError(error.X, f())
expectFmt(esperado, fmt, args)Verifica saída formatadatry expectFmt("42", "{d}", .{42})

Allocator de Teste

O std.testing.allocator detecta vazamentos de memória automaticamente — se o teste terminar com memória não liberada, o teste falha.

const std = @import("std");

test "sem vazamento de memória" {
    const allocator = std.testing.allocator;

    var lista = std.ArrayList(u8).init(allocator);
    defer lista.deinit(); // Se esquecer, o teste FALHA

    try lista.append('a');
    try lista.append('b');
    try lista.append('c');

    try std.testing.expectEqual(@as(usize, 3), lista.items.len);
}

Teste com Setup e Teardown

Zig não tem beforeEach/afterEach formal, mas você pode usar funções auxiliares:

const std = @import("std");

const Contexto = struct {
    allocator: std.mem.Allocator,
    buffer: []u8,

    fn init(allocator: std.mem.Allocator) !Contexto {
        return .{
            .allocator = allocator,
            .buffer = try allocator.alloc(u8, 1024),
        };
    }

    fn deinit(self: *Contexto) void {
        self.allocator.free(self.buffer);
    }
};

test "com setup e teardown" {
    var ctx = try Contexto.init(std.testing.allocator);
    defer ctx.deinit();

    // usar ctx.buffer no teste...
    ctx.buffer[0] = 42;
    try std.testing.expectEqual(@as(u8, 42), ctx.buffer[0]);
}

Testes em Módulos Separados

Testando código de outro arquivo

// Em math.zig
pub fn fatorial(n: u64) u64 {
    if (n <= 1) return 1;
    return n * fatorial(n - 1);
}

test "fatorial de 5" {
    try std.testing.expectEqual(@as(u64, 120), fatorial(5));
}

Referenciando testes de outros módulos

// Em tests.zig — importar para executar testes de múltiplos módulos
const std = @import("std");

// Ao referenciar esses módulos, seus testes também são incluídos
comptime {
    _ = @import("math.zig");
    _ = @import("utils.zig");
    _ = @import("parser.zig");
}

test "teste adicional centralizado" {
    // ...
}

Executar todos: zig test tests.zig

Testes Condicionais

Pular testes com return

const builtin = @import("builtin");

test "apenas em Linux" {
    if (builtin.os.tag != .linux) return; // pula em outros SOs

    // código específico de Linux...
    try std.testing.expect(true);
}

test "apenas em modo debug" {
    if (builtin.mode != .Debug) return;

    // verificações que só fazem sentido em debug
    try std.testing.expect(true);
}

Comandos do Test Runner

# Rodar todos os testes de um arquivo
zig test arquivo.zig

# Rodar com filtro de nome
zig test arquivo.zig --test-filter "fatorial"

# Rodar em modo release
zig test arquivo.zig -OReleaseSafe

# Com build.zig
zig build test

# Verbose — ver quais testes rodaram
zig test arquivo.zig --verbose

Testes no build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Compilar o executável principal
    const exe = b.addExecutable(.{
        .name = "meu-app",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(exe);

    // Configurar testes
    const testes = b.addTest(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    const run_testes = b.addRunArtifact(testes);

    // Step "test" — executar com `zig build test`
    const test_step = b.step("test", "Executar testes unitários");
    test_step.dependOn(&run_testes.step);
}

Padrões Úteis para Testes

Teste de tabela (table-driven tests)

test "somar — table driven" {
    const casos = .{
        .{ 1, 2, 3 },
        .{ 0, 0, 0 },
        .{ -1, 1, 0 },
        .{ 100, -100, 0 },
    };

    inline for (casos) |caso| {
        try std.testing.expectEqual(@as(i32, caso[2]), somar(caso[0], caso[1]));
    }
}

Teste com timeout (via build.zig)

// No build.zig
const run_testes = b.addRunArtifact(testes);
run_testes.max_rss = 256 * 1024 * 1024; // limite de 256MB RAM

Erros Comuns em Testes

// ERRO: Esquecer o try nas asserções
test "sem try" {
    // expect(true);  // ERRADO — ignora o erro
    try expect(true); // CORRETO
}

// ERRO: Tipo errado no expectEqual
test "tipos diferentes" {
    // expectEqual(42, funcao());  // pode falhar por tipo
    try expectEqual(@as(i32, 42), funcao()); // CORRETO — tipo explícito
}

// ERRO: Vazamento de memória com testing.allocator
test "vazamento" {
    const alloc = std.testing.allocator;
    const buf = try alloc.alloc(u8, 100);
    defer alloc.free(buf); // SEM ISSO, o teste falha
}

Veja Também

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.