Testes Baseados em Propriedades com Zig

Testes Baseados em Propriedades com Zig

Testes tradicionais verificam exemplos específicos. Testes baseados em propriedades verificam que uma propriedade vale para QUALQUER entrada. Em vez de testar que sort([3,1,2]) == [1,2,3], testamos que “para qualquer array, o resultado de sort é ordenado”.

Propriedades vs Exemplos

const std = @import("std");

fn meuSort(slice: []u32) void {
    std.mem.sort(u32, slice, {}, std.sort.asc(u32));
}

// Teste por exemplos (tradicional)
test "sort: exemplo específico" {
    var dados = [_]u32{ 3, 1, 4, 1, 5, 9, 2, 6 };
    meuSort(&dados);
    try std.testing.expectEqualSlices(u32, &.{ 1, 1, 2, 3, 4, 5, 6, 9 }, &dados);
}

// Teste por propriedades (abrangente)
test "propriedade: sort produz resultado ordenado" {
    var rng = std.Random.DefaultPrng.init(42);

    for (0..1000) |_| {
        // Gerar array aleatório
        var dados: [100]u32 = undefined;
        for (&dados) |*v| v.* = rng.random().int(u32);

        const original_len = dados.len;
        meuSort(&dados);

        // Propriedade 1: resultado é ordenado
        for (1..dados.len) |i| {
            try std.testing.expect(dados[i - 1] <= dados[i]);
        }

        // Propriedade 2: tamanho não muda
        try std.testing.expectEqual(original_len, dados.len);
    }
}

test "propriedade: sort é idempotente" {
    var rng = std.Random.DefaultPrng.init(123);

    for (0..1000) |_| {
        var dados: [50]u32 = undefined;
        for (&dados) |*v| v.* = rng.random().int(u32);

        meuSort(&dados);
        const primeira = dados;

        meuSort(&dados);

        // sort(sort(x)) == sort(x)
        try std.testing.expectEqualSlices(u32, &primeira, &dados);
    }
}

Framework de Property Testing

const std = @import("std");

fn PropertyTest(comptime T: type) type {
    return struct {
        rng: std.Random.DefaultPrng,
        iteracoes: u32,

        const Self = @This();

        pub fn init(seed: u64, iteracoes: u32) Self {
            return .{
                .rng = std.Random.DefaultPrng.init(seed),
                .iteracoes = iteracoes,
            };
        }

        pub fn verificar(
            self: *Self,
            propriedade: *const fn (T) bool,
            gerador: *const fn (*std.Random.DefaultPrng) T,
        ) !void {
            for (0..self.iteracoes) |i| {
                const valor = gerador(&self.rng);
                if (!propriedade(valor)) {
                    std.debug.print("Propriedade violada na iteração {d}\n", .{i});
                    return error.PropriedadeViolada;
                }
            }
        }
    };
}

Propriedades Comuns

Roundtrip (Ida e Volta)

test "propriedade: serialize/deserialize roundtrip" {
    var rng = std.Random.DefaultPrng.init(42);

    for (0..1000) |_| {
        const original = Ponto{
            .x = @bitCast(rng.random().int(u32)),
            .y = @bitCast(rng.random().int(u32)),
        };

        var buf: [8]u8 = undefined;
        original.serializar(&buf);

        const restaurado = Ponto.deserializar(&buf);

        // deserialize(serialize(x)) == x
        try std.testing.expectEqual(original.x, restaurado.x);
        try std.testing.expectEqual(original.y, restaurado.y);
    }
}

Invariantes de Estrutura de Dados

test "propriedade: HashMap mantém todas as chaves inseridas" {
    var rng = std.Random.DefaultPrng.init(42);
    var map = std.AutoHashMap(u32, u32).init(std.testing.allocator);
    defer map.deinit();

    var chaves_inseridas = std.ArrayList(u32).init(std.testing.allocator);
    defer chaves_inseridas.deinit();

    // Inserir N chaves aleatórias
    for (0..500) |_| {
        const chave = rng.random().int(u32);
        const valor = rng.random().int(u32);
        try map.put(chave, valor);
        try chaves_inseridas.append(chave);
    }

    // Propriedade: toda chave inserida deve ser encontrável
    for (chaves_inseridas.items) |chave| {
        try std.testing.expect(map.contains(chave));
    }
}

Conclusão

Testes baseados em propriedades complementam testes por exemplos ao verificar invariantes sobre espaços enormes de entradas possíveis. Em Zig, a combinação de std.Random para geração de dados, loops de verificação e o fuzzer built-in permite criar suítes de property testing poderosas sem dependências externas.

Conteúdo Relacionado

Continue aprendendo Zig

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