zig test em Zig — O que é e Como Usar

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

  1. Testes integrados: Blocos test vivem ao lado do código que testam, facilitando a manutenção.
  2. Sem framework externo: O sistema de testes é parte da linguagem e da toolchain.
  3. Rápido: O compilador só compila o que é necessário para os testes.
  4. 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çãoDescrição
--test-filter "nome"Executar apenas testes cujo nome contém a string
--test-cmd ARGPassar argumentos ao executor de testes
-Doptimize=MODETestar em modo de otimização específico
--summary allMostrar resumo detalhado

Armadilhas Comuns

  • Testes em arquivos importados: zig test executa 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.allocator em testes — ele detecta leaks e double-free automaticamente.
  • Efeitos colaterais: Testes devem ser independentes. Evitar estado global mutável entre testes.
  • Testes comptime: Blocos test rodam em runtime. Para validação em comptime, use comptime { ... } 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.

Tutoriais Relacionados

Continue aprendendo Zig

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