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
- Fuzz Testing em Zig — Fuzzing
- Property Testing em Zig — Tutorial detalhado
- Unit Tests em Zig — Fundamentos
- Test Patterns — Padrões de teste
- CI/CD com Zig — Automação