---
title: "Property-Based Testing em Zig: Testes Baseados em Propriedades"
url: "https://ziglang.com.br/tutoriais/zig-property-testing/"
markdown_url: "https://ziglang.com.br/tutoriais/zig-property-testing.MD"
description: "Aprenda a escrever testes mais robustos em Zig usando property-based testing. Gere casos de teste automaticamente, encontre edge cases e teste invariantes em vez de exemplos específicos."
date: "2026-02-11"
author: ""
---

# Property-Based Testing em Zig: Testes Baseados em Propriedades

Aprenda a escrever testes mais robustos em Zig usando property-based testing. Gere casos de teste automaticamente, encontre edge cases e teste invariantes em vez de exemplos específicos.


**Property-Based Testing (PBT)** é uma técnica onde você **define propriedades** que seu código deve satisfazer, e o framework gera automaticamente **centenas ou milhares de casos de teste** para verificar essas propriedades.

Em vez de testar: `reverse([1,2,3]) == [3,2,1]`  
Você testa: `reverse(reverse(x)) == x` para **qualquer x**

Neste guia, você aprenderá a implementar PBT em Zig desde o básico até técnicas avançadas.

## Por que Property-Based Testing?

| Aspecto | Unit Testing | Property-Based Testing |
|---------|--------------|----------------------|
| **Casos de teste** | Manuais (você escreve) | Gerados automaticamente |
| **Cobertura** | Limitada pelos exemplos | Explora espaço de entrada |
| **Edge cases** | Você precisa imaginar | Descobertos automaticamente |
| **Manutenção** | Alto (atualiza exemplos) | Baixo (propriedades raramente mudam) |
| **Documentação** | Exemplos específicos | Regras gerais |

## Implementando Geradores

### Gerador Básico

```zig
const std = @import("std");
const rand = std.rand;
const testing = std.testing;

// Tipo para funções geradoras
fn Generator(comptime T: type) type {
    return *const fn (
        rand: std.Random,
        allocator: std.mem.Allocator,
    ) anyerror!T;
}

// Gerador de inteiros com range
fn genIntRange(
    comptime T: type,
    min: T,
    max: T,
) Generator(T) {
    return struct {
        fn generate(r: std.Random, _: std.mem.Allocator) !T {
            return r.intRangeLessThan(T, min, max);
        }
    }.generate;
}

// Gerador de arrays
fn genArray(
    comptime T: type,
    min_len: usize,
    max_len: usize,
    elem_gen: Generator(T),
) Generator([]T) {
    return struct {
        fn generate(r: std.Random, a: std.mem.Allocator) ![]T {
            const len = r.intRangeLessThan(usize, min_len, max_len);
            const arr = try a.alloc(T, len);
            errdefer a.free(arr);
            
            for (arr) |*elem| {
                elem.* = try elem_gen(r, a);
            }
            
            return arr;
        }
    }.generate;
}
```

### Geradores Comuns

```zig
// Gerador de inteiros positivos
fn genU32(r: std.Random, _: std.mem.Allocator) !u32 {
    return r.int(u32);
}

// Gerador de strings ASCII
fn genString(r: std.Random, a: std.mem.Allocator) ![]u8 {
    const len = r.intRangeLessThan(usize, 0, 100);
    const str = try a.alloc(u8, len);
    errdefer a.free(str);
    
    for (str) |*c| {
        c.* = r.intRangeLessThan(u8, 'a', 'z' + 1);
    }
    
    return str;
}

// Gerador de floats
fn genF64(r: std.Random, _: std.mem.Allocator) !f64 {
    return r.float(f64);
}

// Gerador de enums
fn genEnum(comptime E: type) Generator(E) {
    return struct {
        fn generate(r: std.Random, _: std.mem.Allocator) !E {
            const values = std.enums.values(E);
            const idx = r.intRangeLessThan(usize, 0, values.len);
            return values[idx];
        }
    }.generate;
}

// Gerador de Optionals
fn genOptional(
    comptime T: type,
    inner_gen: Generator(T),
) Generator(?T) {
    return struct {
        fn generate(r: std.Random, a: std.mem.Allocator) !?T {
            if (r.boolean()) {
                return try inner_gen(r, a);
            }
            return null;
        }
    }.generate;
}
```

## Framework de Property Testing

```zig
// Estrutura principal de teste de propriedades
pub const PropertyTest = struct {
    allocator: std.mem.Allocator,
    rng: std.Random,
    
    pub fn init(allocator: std.mem.Allocator, seed: u64) !PropertyTest {
        var prng = std.rand.DefaultPrng.init(seed);
        
        return .{
            .allocator = allocator,
            .rng = prng.random(),
        };
    }
    
    // Testa uma propriedade com múltiplos casos gerados
    pub fn property(
        self: *PropertyTest,
        comptime T: type,
        gen: Generator(T),
        comptime iterations: usize,
        check: fn (T) bool,
    ) !void {
        var i: usize = 0;
        while (i < iterations) : (i += 1) {
            const value = try gen(self.rng, self.allocator);
            defer if (@typeInfo(T) == .Pointer) {
                self.allocator.free(value);
            };
            
            if (!check(value)) {
                std.debug.print(
                    "Propriedade falhou no caso {d}: {any}\n",
                    .{ i, value }
                );
                return error.PropertyFailed;
            }
        }
    }
    
    // Testa propriedade com dois geradores
    pub fn property2(
        self: *PropertyTest,
        comptime T1: type,
        comptime T2: type,
        gen1: Generator(T1),
        gen2: Generator(T2),
        comptime iterations: usize,
        check: fn (T1, T2) bool,
    ) !void {
        var i: usize = 0;
        while (i < iterations) : (i += 1) {
            const v1 = try gen1(self.rng, self.allocator);
            defer if (@typeInfo(T1) == .Pointer) self.allocator.free(v1);
            
            const v2 = try gen2(self.rng, self.allocator);
            defer if (@typeInfo(T2) == .Pointer) self.allocator.free(v2);
            
            if (!check(v1, v2)) {
                std.debug.print(
                    "Propriedade falhou: v1={any}, v2={any}\n",
                    .{ v1, v2 }
                );
                return error.PropertyFailed;
            }
        }
    }
};
```

## Propriedades Comuns

### 1. Inversão (Round-trip)

```zig
// Se encode seguido de decode deve retornar o original
fn testRoundTrip() !void {
    var pt = try PropertyTest.init(testing.allocator, 42);
    
    try pt.property(
        []const u8,
        genString,
        100,
        struct {
            fn check(input: []const u8) bool {
                const encoded = base64Encode(input);
                const decoded = base64Decode(encoded);
                return std.mem.eql(u8, input, decoded);
            }
        }.check
    );
}
```

### 2. Idempotência

```zig
// Aplicar duas vezes é igual a aplicar uma vez
fn testIdempotent() !void {
    var pt = try PropertyTest.init(testing.allocator, 42);
    
    try pt.property(
        []const u8,
        genString,
        100,
        struct {
            fn check(s: []const u8) bool {
                const once = trim(s);
                const twice = trim(once);
                return std.mem.eql(u8, once, twice);
            }
        }.check
    );
}
```

### 3. Simetria

```zig
// Operações inversas se anulam
fn testSymmetry() !void {
    var pt = try PropertyTest.init(testing.allocator, 42);
    
    try pt.property(
        i32,
        genIntRange(i32, -1000, 1000),
        1000,
        struct {
            fn check(x: i32) bool {
                return negate(negate(x)) == x;
            }
        }.check
    );
}
```

### 4. Ordem/Monotonicidade

```zig
// Sort preserva ordem
fn testSortOrder() !void {
    var pt = try PropertyTest.init(testing.allocator, 42);
    
    const IntArrayGen = genArray(i32, 0, 50, genIntRange(i32, -100, 100));
    
    try pt.property(
        []i32,
        IntArrayGen,
        100,
        struct {
            fn check(arr: []i32) bool {
                const sorted = sort(arr);
                
                // Verifica ordenação
                for (sorted[0 .. sorted.len - 1], sorted[1..]) |a, b| {
                    if (a > b) return false;
                }
                
                // Verifica que é permutação
                return sameElements(arr, sorted);
            }
        }.check
    );
}
```

### 5. Algebraic Properties

```zig
// Testa propriedades algébricas
fn testAlgebraic() !void {
    var pt = try PropertyTest.init(testing.allocator, 42);
    
    const IntGen = genIntRange(i32, -100, 100);
    
    // Comutatividade: a + b == b + a
    try pt.property2(
        i32, i32,
        IntGen, IntGen,
        100,
        struct {
            fn check(a: i32, b: i32) bool {
                return add(a, b) == add(b, a);
            }
        }.check
    );
    
    // Associatividade: (a + b) + c == a + (b + c)
    // Implementação similar com property3
    
    // Elemento neutro: a + 0 == a
    try pt.property(
        i32,
        IntGen,
        100,
        struct {
            fn check(a: i32) bool {
                return add(a, 0) == a;
            }
        }.check
    );
}
```

## Casos de Teste Reais

### Testando uma Stack

```zig
const Stack = struct {
    items: std.ArrayList(i32),
    
    pub fn push(self: *Stack, item: i32) void {
        self.items.append(item) catch unreachable;
    }
    
    pub fn pop(self: *Stack) ?i32 {
        return self.items.popOrNull();
    }
    
    pub fn peek(self: Stack) ?i32 {
        if (self.items.items.len == 0) return null;
        return self.items.items[self.items.items.len - 1];
    }
    
    pub fn len(self: Stack) usize {
        return self.items.items.len;
    }
};

test "stack properties" {
    var pt = try PropertyTest.init(testing.allocator, 42);
    
    // Propriedade: push seguido de pop retorna o elemento
    try pt.property(
        i32,
        genIntRange(i32, -10000, 10000),
        100,
        struct {
            fn check(x: i32) bool {
                var stack = Stack{ .items = std.ArrayList(i32).init(testing.allocator) };
                defer stack.items.deinit();
                
                stack.push(x);
                return stack.pop() == x;
            }
        }.check
    );
    
    // Propriedade: peek não modifica a stack
    try pt.property(
        []i32,
        genArray(i32, 1, 20, genIntRange(i32, -100, 100)),
        50,
        struct {
            fn check(items: []i32) bool {
                var stack = Stack{ .items = std.ArrayList(i32).init(testing.allocator) };
                defer stack.items.deinit();
                
                for (items) |item| stack.push(item);
                
                const before = stack.len();
                _ = stack.peek();
                const after = stack.len();
                
                return before == after;
            }
        }.check
    );
}
```

### Testando Parser JSON

```zig
test "json parser properties" {
    var pt = try PropertyTest.init(testing.allocator, 42);
    
    // Propriedade: parse de string válida não falha
    try pt.property(
        []const u8,
        genJsonString, // Gerador de JSON válido
        100,
        struct {
            fn check(json: []const u8) bool {
                _ = parseJson(json) catch |err| switch (err) {
                    error.InvalidJson => return false,
                    else => return true, // Outros erros são aceitáveis
                };
                return true;
            }
        }.check
    );
    
    // Propriedade: parse e stringify são inversos
    try pt.property(
        JsonValue,
        genJsonValue,
        50,
        struct {
            fn check(value: JsonValue) bool {
                const str = stringify(value);
                const parsed = parseJson(str) catch return false;
                return valuesEqual(value, parsed);
            }
        }.check
    );
}
```

## Shrinking (Minimização de Casos de Falha)

Quando uma propriedade falha, queremos o **menor caso** que causa a falha:

```zig
fn shrinkInt(x: i32) []const i32 {
    var candidates = std.ArrayList(i32).init(testing.allocator);
    defer candidates.deinit();
    
    // Tenta metade
    if (x != 0) {
        candidates.append(x / 2) catch {};
        candidates.append(-x / 2) catch {};
    }
    
    // Tenta valores próximos de zero
    if (x > 0) candidates.append(x - 1) catch {};
    if (x < 0) candidates.append(x + 1) catch {};
    
    // Tenta zero
    candidates.append(0) catch {};
    
    return candidates.toOwnedSlice() catch &[]i32{};
}

fn findMinimal(
    comptime T: type,
    value: T,
    shrink: fn (T) []const T,
    property: fn (T) bool,
) T {
    var minimal = value;
    var candidates = shrink(value);
    defer testing.allocator.free(candidates);
    
    for (candidates) |candidate| {
        if (!property(candidate)) {
            // Encontrou caso menor que falha
            const smaller = findMinimal(T, candidate, shrink, property);
            return smaller;
        }
    }
    
    return minimal;
}
```

## Integração com CI

```zig
// test_properties.zig
const std = @import("std");

pub fn main() !void {
    const seed = std.time.milliTimestamp();
    std.debug.print("Seed: {d}\n", .{seed});
    
    var pt = try PropertyTest.init(std.heap.page_allocator, @intCast(seed));
    
    // Roda todas as propriedades
    try testRoundTrip(&pt);
    try testSymmetry(&pt);
    try testAlgebraic(&pt);
    try testStack(&pt);
    
    std.debug.print("✅ Todas as propriedades passaram!\n");
}
```

## Próximos Passos

1. 🔗 **[Testes Unitários em Zig](/tutoriais/testes-zig/)** — Fundamentos de testes
2. ⚡ **[Debug em Zig](/tutoriais/zig-debugging/)** — Debug quando propriedades falham
3. 🧠 **[Comptime em Zig](/tutoriais/comptime-em-zig/)** — Geração de testes em compile-time
4. 📦 **[CI/CD para Zig](/tutoriais/zig-producao-deployment/)** — Integração property tests em pipeline

---

*Quais propriedades você testa em seus projetos? Compartilhe padrões!*
