Perguntas de Entrevista sobre Testing em Zig
Testes são fundamentais em qualquer base de código profissional. O Zig tem suporte a testes integrado diretamente na linguagem com blocos test, um allocator de teste que detecta memory leaks e integração com o build system. Este guia cobre perguntas práticas sobre testing em Zig para entrevistas.
Perguntas e Respostas
1. Como funciona o sistema de testes do Zig?
No Zig, testes são blocos test "nome" que vivem junto ao código-fonte. O compilador pode executá-los via zig test arquivo.zig. Os blocos test não são compilados em builds normais — apenas quando zig test é executado.
const std = @import("std");
fn somar(a: i32, b: i32) i32 {
return a + b;
}
test "somar dois positivos" {
try std.testing.expectEqual(@as(i32, 5), somar(2, 3));
}
test "somar com zero" {
try std.testing.expectEqual(@as(i32, 7), somar(7, 0));
}
test "somar negativos" {
try std.testing.expectEqual(@as(i32, -3), somar(-1, -2));
}
Cada bloco test é executado isoladamente. Se um try falha, o teste é reportado como falho com informação detalhada sobre o valor esperado vs. recebido.
2. Qual a diferença entre expect, expectEqual e expectEqualStrings?
const testing = std.testing;
test "expect — verifica booleano" {
const x: i32 = 10;
try testing.expect(x > 5); // Verifica condição booleana
try testing.expect(x != 0);
}
test "expectEqual — verifica igualdade de valores" {
try testing.expectEqual(@as(i32, 42), 42); // Inteiros
try testing.expectEqual(@as(f64, 3.14), 3.14); // Floats exatos
try testing.expectEqual(@as(?i32, null), @as(?i32, null)); // Optionals
}
test "expectEqualStrings — compara conteúdo de strings" {
const nome = "Zig";
try testing.expectEqualStrings("Zig", nome);
// Diferente de expectEqual, mostra diff detalhado em caso de falha
}
test "expectEqualSlices — compara slices elemento a elemento" {
const esperado = [_]u8{ 1, 2, 3 };
const obtido = [_]u8{ 1, 2, 3 };
try testing.expectEqualSlices(u8, &esperado, &obtido);
}
test "expectApproxEqAbs — floats com tolerância" {
try testing.expectApproxEqAbs(@as(f64, 3.14159), 3.14, 0.01);
}
test "expectError — verifica que erro específico é retornado" {
const resultado: anyerror!i32 = error.Overflow;
try testing.expectError(error.Overflow, resultado);
}
3. O que é o testing allocator e por que é importante?
O std.testing.allocator é um allocator especial que rastreia todas as alocações e, ao final do teste, verifica se toda memória foi liberada. Se houver leak, o teste falha automaticamente.
const std = @import("std");
const testing = std.testing;
fn criarLista(allocator: std.mem.Allocator) !std.ArrayList(i32) {
var lista = std.ArrayList(i32).init(allocator);
try lista.append(1);
try lista.append(2);
try lista.append(3);
return lista;
}
test "lista sem memory leak" {
var lista = try criarLista(testing.allocator);
defer lista.deinit(); // Sem isso, o teste FALHA!
try testing.expectEqual(@as(usize, 3), lista.items.len);
}
test "demonstração de leak detection" {
// Se comentar o defer, o teste falha com:
// "memory leak detected" + endereço e stack trace da alocação
var buf = try testing.allocator.alloc(u8, 100);
defer testing.allocator.free(buf);
buf[0] = 42;
try testing.expectEqual(@as(u8, 42), buf[0]);
}
4. Como organizar testes em projetos maiores?
// src/math.zig
pub fn fatorial(n: u64) u64 {
if (n <= 1) return 1;
var r: u64 = 1;
for (2..n + 1) |i| r *= i;
return r;
}
// Testes inline — vivem junto ao código
test "fatorial" {
const testing = @import("std").testing;
try testing.expectEqual(@as(u64, 1), fatorial(0));
try testing.expectEqual(@as(u64, 120), fatorial(5));
}
// src/main.zig — referencia testes de submódulos
const math = @import("math.zig");
test {
// Força o compilador a incluir testes de todos os imports
@import("std").testing.refAllDecls(@This());
_ = math; // Inclui testes de math.zig
}
No build.zig, configure testes com:
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Executa testes unitários");
test_step.dependOn(&run_unit_tests.step);
5. Como testar código que depende de I/O ou estado externo?
No Zig, a abordagem idiomática é injetar dependências via parâmetros (dependency injection):
const std = @import("std");
/// Processador que aceita qualquer Writer (injeção de dependência).
pub fn processar(dados: []const u8, writer: anytype) !usize {
var contagem: usize = 0;
for (dados) |c| {
if (c >= 'A' and c <= 'Z') {
try writer.writeByte(c + 32); // lowercase
contagem += 1;
} else {
try writer.writeByte(c);
}
}
return contagem;
}
test "processar converte maiúsculas" {
var buf: [256]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
const writer = stream.writer();
const convertidos = try processar("Hello WORLD", writer);
try std.testing.expectEqual(@as(usize, 7), convertidos);
try std.testing.expectEqualStrings("hello world", stream.getWritten());
}
6. Como simular (mock) dependências em testes?
O Zig não tem um framework de mocking nativo, mas comptime generics permitem injeção limpa:
const std = @import("std");
const testing = std.testing;
/// Interface genérica via comptime — aceita implementação real ou mock.
fn HttpHandler(comptime Fetcher: type) type {
return struct {
pub fn obterStatus(fetcher: *Fetcher, url: []const u8) !u16 {
const response = try fetcher.fetch(url);
return response.status;
}
};
}
// Mock para testes
const MockFetcher = struct {
chamadas: usize = 0,
const Response = struct { status: u16 };
pub fn fetch(self: *MockFetcher, url: []const u8) !Response {
self.chamadas += 1;
_ = url;
return .{ .status = 200 };
}
};
test "HttpHandler com mock" {
var mock = MockFetcher{};
const Handler = HttpHandler(MockFetcher);
const status = try Handler.obterStatus(&mock, "https://example.com");
try testing.expectEqual(@as(u16, 200), status);
try testing.expectEqual(@as(usize, 1), mock.chamadas);
}
7. Como testar código concorrente em Zig?
const std = @import("std");
const testing = std.testing;
fn Contador() type {
return struct {
const Self = @This();
valor: std.atomic.Value(u32),
pub fn init() Self {
return .{ .valor = std.atomic.Value(u32).init(0) };
}
pub fn incrementar(self: *Self) void {
_ = self.valor.fetchAdd(1, .monotonic);
}
pub fn obter(self: *const Self) u32 {
return self.valor.load(.monotonic);
}
};
}
test "contador atômico com múltiplas threads" {
var contador = Contador().init();
const num_threads = 4;
const incrementos_por_thread = 1000;
var threads: [num_threads]std.Thread = undefined;
for (&threads) |*t| {
t.* = try std.Thread.spawn(.{}, struct {
fn func(c: *Contador()) void {
for (0..incrementos_por_thread) |_| {
c.incrementar();
}
}
}.func, .{&contador});
}
for (threads) |t| t.join();
try testing.expectEqual(
@as(u32, num_threads * incrementos_por_thread),
contador.obter(),
);
}
8. Como usar zig test com filtros e opções?
# Executar todos os testes
zig test src/main.zig
# Filtrar por nome do teste
zig test src/main.zig --test-filter "fatorial"
# Com sumário detalhado
zig test src/main.zig --summary all
# Sem captura de stderr (mostra std.debug.print)
zig test src/main.zig 2>&1
9. Como testar funções que podem causar panic?
No Zig, @panic não é catchable em código normal, mas em testes existe uma técnica usando std.testing.expectEqual com comportamento de runtime:
const std = @import("std");
fn dividir(a: i32, b: i32) i32 {
if (b == 0) @panic("divisão por zero");
return @divTrunc(a, b);
}
test "dividir funciona normalmente" {
try std.testing.expectEqual(@as(i32, 5), dividir(10, 2));
try std.testing.expectEqual(@as(i32, -3), dividir(-9, 3));
}
// Para testar panic, a abordagem idiomática é usar error unions
// em vez de @panic, para que erros sejam testáveis:
fn dividirSeguro(a: i32, b: i32) !i32 {
if (b == 0) return error.DivisaoPorZero;
return @divTrunc(a, b);
}
test "dividirSeguro retorna erro para zero" {
try std.testing.expectError(error.DivisaoPorZero, dividirSeguro(10, 0));
}
10. Como escrever testes de propriedade (property-based testing)?
const std = @import("std");
const testing = std.testing;
fn inverterSlice(comptime T: type, slice: []T) void {
if (slice.len <= 1) return;
var i: usize = 0;
var j = slice.len - 1;
while (i < j) : ({
i += 1;
j -= 1;
}) {
const tmp = slice[i];
slice[i] = slice[j];
slice[j] = tmp;
}
}
test "propriedade: inverter duas vezes volta ao original" {
var prng = std.Random.DefaultPrng.init(42);
const random = prng.random();
// Testa com múltiplas entradas aleatórias
for (0..100) |_| {
const tamanho = random.intRangeAtMost(usize, 0, 50);
var dados = try testing.allocator.alloc(u8, tamanho);
defer testing.allocator.free(dados);
// Preenche com dados aleatórios
random.bytes(dados);
// Salva cópia do original
var original = try testing.allocator.alloc(u8, tamanho);
defer testing.allocator.free(original);
@memcpy(original, dados);
// Inverte duas vezes
inverterSlice(u8, dados);
inverterSlice(u8, dados);
// Propriedade: deve voltar ao original
try testing.expectEqualSlices(u8, original, dados);
}
}
test "propriedade: ordenar é idempotente" {
var prng = std.Random.DefaultPrng.init(123);
const random = prng.random();
for (0..50) |_| {
const tamanho = random.intRangeAtMost(usize, 1, 30);
var dados = try testing.allocator.alloc(i32, tamanho);
defer testing.allocator.free(dados);
for (dados) |*d| d.* = random.int(i32);
// Ordena
std.mem.sort(i32, dados, {}, std.sort.asc(i32));
// Propriedade: já está ordenado
try testing.expect(std.sort.isSorted(i32, dados, {}, std.sort.asc(i32)));
}
}
11. Como medir cobertura de testes em Zig?
O Zig possui suporte experimental a cobertura de código via LLVM:
# Compila com instrumentação de cobertura
zig test -fllvm --coverage src/main.zig
# Gera relatório (usando llvm-cov)
llvm-profdata merge default.profraw -o default.profdata
llvm-cov show ./zig-out/test -instr-profile=default.profdata
12. Quais as boas práticas de testing em Zig?
- Use
testing.allocatorpara detectar leaks automaticamente - Teste junto ao código — blocos
testno mesmo arquivo - Prefira error unions a @panic para código testável
- Injete dependências via parâmetros genéricos (comptime)
- Nomeie testes descritivamente —
test "fatorial de zero retorna 1" - Use
refAllDeclsno main para incluir testes de submódulos - Teste edge cases — null, vazio, overflow, zero
Recursos Relacionados
- std.testing — Referência completa do módulo
- std.debug — Assertions e depuração
- std.log — Logging para diagnóstico
- Perguntas de Entrevista: Error Handling
- Perguntas de Entrevista: Memória