zig test em Zig — O que é e Como Usar
Definição
zig test é o comando da toolchain do Zig para executar testes unitários definidos diretamente no código-fonte. Em Zig, testes são declarados com a palavra-chave test dentro de qualquer arquivo .zig — não existe necessidade de framework de testes externo. O compilador extrai automaticamente os blocos de teste, compila-os e executa.
Por que zig test Importa
- Testes integrados: Blocos
testvivem ao lado do código que testam, facilitando a manutenção. - Sem framework externo: O sistema de testes é parte da linguagem e da toolchain.
- Rápido: O compilador só compila o que é necessário para os testes.
- Filtros: É possível rodar apenas testes específicos por nome.
Exemplo Prático
Testes Básicos
const std = @import("std");
const expect = std.testing.expect;
fn somar(a: i32, b: i32) i32 {
return a + b;
}
fn dividir(a: f64, b: f64) !f64 {
if (b == 0) return error.DivisaoPorZero;
return a / b;
}
test "somar dois números" {
try expect(somar(2, 3) == 5);
try expect(somar(-1, 1) == 0);
}
test "dividir números válidos" {
const resultado = try dividir(10, 2);
try expect(resultado == 5.0);
}
test "dividir por zero retorna erro" {
const resultado = dividir(10, 0);
try std.testing.expectError(error.DivisaoPorZero, resultado);
}
Executando Testes
# Rodar todos os testes do arquivo
zig test src/math.zig
# Filtrar testes por nome
zig test src/math.zig --test-filter "somar"
# Via sistema de build
zig build test
Testes com Allocator de Teste
const std = @import("std");
const expect = std.testing.expect;
fn duplicarSlice(allocator: std.mem.Allocator, dados: []const u8) ![]u8 {
const resultado = try allocator.alloc(u8, dados.len * 2);
@memcpy(resultado[0..dados.len], dados);
@memcpy(resultado[dados.len..], dados);
return resultado;
}
test "duplicar slice" {
// testing.allocator detecta memory leaks automaticamente
const resultado = try duplicarSlice(std.testing.allocator, "abc");
defer std.testing.allocator.free(resultado);
try expect(std.mem.eql(u8, resultado, "abcabc"));
}
Opções do Comando
| Opção | Descrição |
|---|---|
--test-filter "nome" | Executar apenas testes cujo nome contém a string |
--test-cmd ARG | Passar argumentos ao executor de testes |
-Doptimize=MODE | Testar em modo de otimização específico |
--summary all | Mostrar resumo detalhado |
Armadilhas Comuns
- Testes em arquivos importados:
zig testexecuta testes do arquivo raiz e todos os módulos importados com@import. Testes em arquivos não importados são ignorados. - testing.allocator: Use
std.testing.allocatorem testes — ele detecta leaks e double-free automaticamente. - Efeitos colaterais: Testes devem ser independentes. Evitar estado global mutável entre testes.
- Testes comptime: Blocos
testrodam em runtime. Para validação em comptime, usecomptime { ... }com@compileError.
Termos Relacionados
- zig build — Comando de build que pode executar testes
- build.zig — Configuração de steps de teste
- Error Union — Testes com funções que retornam erros
- Allocator — testing.allocator para detecção de leaks
Organizando Testes em Projetos Maiores
Em projetos com múltiplos arquivos, a prática recomendada é ter os testes de cada módulo no próprio arquivo. Um arquivo raiz de testes pode importar todos os módulos para garantir que os testes sejam coletados:
// src/tests.zig — arquivo que reúne todos os testes do projeto
comptime {
_ = @import("math.zig");
_ = @import("parser.zig");
_ = @import("network.zig");
}
# Rodar todos os testes do projeto via arquivo raiz
zig test src/tests.zig
Via build.zig, a configuração usual é apontar o step de teste para o arquivo principal da aplicação, que importa os demais módulos:
const testes = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_testes = b.addRunArtifact(testes);
b.step("test", "Rodar todos os testes").dependOn(&run_testes.step);
Funções Auxiliares de Testing
A biblioteca padrão oferece várias funções além de expect para facilitar os testes:
const std = @import("std");
const testing = std.testing;
test "funções de teste da std" {
// Comparação de valores
try testing.expectEqual(@as(u32, 42), calcular());
// Comparação de slices
try testing.expectEqualSlices(u8, "esperado", resultado);
// Comparação de strings
try testing.expectEqualStrings("hello", saudacao());
// Verificar que retorna erro específico
try testing.expectError(error.Overflow, funcaoFalha());
// Verificar aproximação de floats
try testing.expectApproxEqAbs(@as(f64, 3.14), calcularPi(), 0.01);
}
Comparação com Outros Sistemas de Teste
Em Rust, os testes são marcados com #[test] e executados com cargo test. Em Go, funções que começam com Test são executadas pelo go test. O Zig usa blocos test "nome" — a principal diferença é que em Zig os testes convivem naturalmente com o código de produção sem necessidade de módulos ou arquivos separados, e o testing.allocator detecta leaks automaticamente sem ferramentas adicionais.