Test Expectations e Matchers em Zig

Introdução

Zig fornece um conjunto de funções de assertion em std.testing que cobrem a maioria dos cenários de teste. Esta receita documenta cada uma com exemplos práticos. Diferente de frameworks como Jest ou JUnit, Zig mantém as assertions simples e explícitas.

Para testes básicos, veja Testes Unitários Básicos. Para testes com memória, consulte Testes com Allocator.

Pré-requisitos

expectEqual

Compara dois valores de mesmo tipo para igualdade:

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

test "expectEqual - inteiros" {
    try testing.expectEqual(@as(i32, 42), soma(20, 22));
}

test "expectEqual - enum" {
    const Estado = enum { ativo, inativo };
    try testing.expectEqual(Estado.ativo, obterEstado());
}

test "expectEqual - struct" {
    const Ponto = struct { x: i32, y: i32 };
    try testing.expectEqual(
        Ponto{ .x = 1, .y = 2 },
        criarPonto(1, 2),
    );
}

test "expectEqual - optional" {
    try testing.expectEqual(@as(?i32, 42), buscar(1));
    try testing.expectEqual(@as(?i32, null), buscar(0));
}

expectEqualStrings

Compara strings (slices de u8) com mensagem de erro clara:

test "expectEqualStrings" {
    try testing.expectEqualStrings("Zig Brasil", formatar("Zig", "Brasil"));
    try testing.expectEqualStrings("", ""); // Strings vazias
}

Se falhar, mostra a diferença exata entre as strings esperada e obtida.

expectEqualSlices

Compara slices elemento por elemento:

test "expectEqualSlices - u8" {
    const esperado = [_]u8{ 1, 2, 3, 4, 5 };
    const obtido = try gerarSequencia(5);
    try testing.expectEqualSlices(u8, &esperado, obtido);
}

test "expectEqualSlices - i32" {
    const esperado = [_]i32{ 10, 20, 30 };
    const obtido = [_]i32{ 10, 20, 30 };
    try testing.expectEqualSlices(i32, &esperado, &obtido);
}

expect (booleano)

Verifica que uma condição é verdadeira:

test "expect" {
    try testing.expect(1 + 1 == 2);
    try testing.expect("abc".len == 3);
    try testing.expect(ehPrimo(7));
    try testing.expect(!ehPrimo(4));
}

expectError

Verifica que uma expressão retorna um erro específico:

fn dividir(a: f64, b: f64) !f64 {
    if (b == 0) return error.DivisaoPorZero;
    return a / b;
}

test "expectError" {
    try testing.expectError(error.DivisaoPorZero, dividir(10, 0));
}

test "sem erro" {
    const resultado = try dividir(10, 2);
    try testing.expectEqual(@as(f64, 5.0), resultado);
}

Veja Error Sets Customizados.

expectApproxEqAbs (floats)

Compara floats com tolerância absoluta:

test "expectApproxEqAbs" {
    const pi = calcularPi();
    try testing.expectApproxEqAbs(@as(f64, 3.14159), pi, 0.001);
}

test "float com tolerância" {
    const resultado = @sin(@as(f64, std.math.pi / 2.0));
    try testing.expectApproxEqAbs(@as(f64, 1.0), resultado, 1e-10);
}

expectApproxEqRel (floats relativos)

Compara floats com tolerância relativa:

test "expectApproxEqRel" {
    const grande = 1_000_000.0;
    const quase_igual = 1_000_000.1;
    try testing.expectApproxEqRel(grande, quase_igual, 1e-6);
}

expectFmt

Verifica a representação formatada de um valor:

test "expectFmt" {
    try testing.expectFmt("42", "{}", .{@as(i32, 42)});
    try testing.expectFmt("hello", "{s}", .{"hello"});
}

Matchers Customizados

Zig não tem um sistema de matchers extensível como Jest, mas você pode criar funções auxiliares:

fn expectMaiorQue(comptime T: type, valor: T, limite: T) !void {
    if (valor <= limite) {
        std.debug.print("Esperava > {}, obteve {}\n", .{ limite, valor });
        return error.TestExpectedGreater;
    }
}

fn expectContem(haystack: []const u8, needle: []const u8) !void {
    if (std.mem.indexOf(u8, haystack, needle) == null) {
        std.debug.print("'{s}' não contém '{s}'\n", .{ haystack, needle });
        return error.TestExpectedContains;
    }
}

fn expectEntre(comptime T: type, valor: T, min: T, max: T) !void {
    if (valor < min or valor > max) {
        std.debug.print("Esperava {} entre {} e {}\n", .{ valor, min, max });
        return error.TestExpectedInRange;
    }
}

test "matchers customizados" {
    try expectMaiorQue(i32, 10, 5);
    try expectContem("Zig Brasil", "Brasil");
    try expectEntre(f64, 3.14, 3.0, 4.0);
}

Verificar Que NÃO Retorna Erro

test "operação bem-sucedida" {
    // try já verifica - se houver erro, o teste falha
    const resultado = try operacao();
    try testing.expect(resultado > 0);
}

Testar Múltiplos Cenários

test "tabela de testes" {
    const casos = [_]struct {
        entrada: i32,
        esperado: i32,
    }{
        .{ .entrada = 0, .esperado = 0 },
        .{ .entrada = 1, .esperado = 1 },
        .{ .entrada = 5, .esperado = 120 },
        .{ .entrada = 10, .esperado = 3628800 },
    };

    for (casos) |caso| {
        const resultado = fatorial(caso.entrada);
        try testing.expectEqual(caso.esperado, resultado);
    }
}

Conclusão

As assertions de std.testing cobrem a grande maioria dos cenários de teste. Para casos especiais, crie funções auxiliares que encapsulem verificações complexas. Mantenha os testes simples e focados em um comportamento por teste.

Para mais sobre testes, veja Testes Unitários Básicos, Testes com Allocator e Mocking e Stubbing.

Continue aprendendo Zig

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