std.testing — Framework de Testes
O módulo std.testing é o framework de testes integrado ao Zig. Ele fornece funções de asserção, um allocator de teste que detecta leaks de memória e integração direta com o sistema de build. Testes em Zig são blocos test que vivem junto ao código-fonte e são executados com zig test.
Visão Geral
const std = @import("std");
const testing = std.testing;
Testes no Zig são definidos com a keyword test e podem conter qualquer código, incluindo chamadas a funções do módulo sendo testado. O runner de testes executa cada bloco test isoladamente.
Funções de Asserção Principais
// Verifica se expressão é verdadeira
pub fn expect(ok: bool) !void
// Verifica igualdade de valores
pub fn expectEqual(expected: anytype, actual: anytype) !void
// Verifica igualdade de slices
pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void
// Verifica igualdade de strings
pub fn expectEqualStrings(expected: []const u8, actual: []const u8) !void
// Verifica que valor é aproximadamente igual (floats)
pub fn expectApproxEqAbs(comptime T: type, expected: T, actual: T, tolerance: T) !void
pub fn expectApproxEqRel(comptime T: type, expected: T, actual: T, tolerance: T) !void
// Verifica que valor é null/não-null
pub fn expectEqual(expected: @TypeOf(null), actual: anytype) !void
// Verifica que uma expressão retorna erro
pub fn expectError(expected: anyerror, actual: anytype) !void
// Allocator de teste (detecta memory leaks)
pub const allocator: std.mem.Allocator = ...;
Exemplo 1: Testes Básicos
const std = @import("std");
const testing = std.testing;
/// Calcula o fatorial de n.
fn fatorial(n: u64) u64 {
if (n == 0) return 1;
var resultado: u64 = 1;
for (1..n + 1) |i| {
resultado *= i;
}
return resultado;
}
/// Verifica se uma string é palíndromo.
fn ehPalindromo(s: []const u8) bool {
if (s.len <= 1) return true;
var i: usize = 0;
var j: usize = s.len - 1;
while (i < j) {
if (s[i] != s[j]) return false;
i += 1;
j -= 1;
}
return true;
}
/// Inverte uma string in-place.
fn inverter(buf: []u8) void {
if (buf.len <= 1) return;
var i: usize = 0;
var j: usize = buf.len - 1;
while (i < j) {
const temp = buf[i];
buf[i] = buf[j];
buf[j] = temp;
i += 1;
j -= 1;
}
}
test "fatorial de casos base" {
try testing.expectEqual(@as(u64, 1), fatorial(0));
try testing.expectEqual(@as(u64, 1), fatorial(1));
}
test "fatorial de valores conhecidos" {
try testing.expectEqual(@as(u64, 120), fatorial(5));
try testing.expectEqual(@as(u64, 3628800), fatorial(10));
try testing.expectEqual(@as(u64, 2432902008176640000), fatorial(20));
}
test "palindromo" {
try testing.expect(ehPalindromo("aba"));
try testing.expect(ehPalindromo("abba"));
try testing.expect(ehPalindromo("a"));
try testing.expect(ehPalindromo(""));
try testing.expect(!ehPalindromo("abc"));
try testing.expect(!ehPalindromo("ab"));
}
test "inverter string" {
var buf = "abcdef".*;
inverter(&buf);
try testing.expectEqualStrings("fedcba", &buf);
var buf2 = "a".*;
inverter(&buf2);
try testing.expectEqualStrings("a", &buf2);
}
Exemplo 2: Testando com Allocator de Teste
const std = @import("std");
const testing = std.testing;
const Allocator = std.mem.Allocator;
/// Lista dinâmica simplificada.
fn Lista(comptime T: type) type {
return struct {
const Self = @This();
items: []T,
capacidade: usize,
tamanho: usize,
allocator: Allocator,
pub fn init(allocator: Allocator) Self {
return .{
.items = &[_]T{},
.capacidade = 0,
.tamanho = 0,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
if (self.capacidade > 0) {
self.allocator.free(self.items.ptr[0..self.capacidade]);
}
}
pub fn adicionar(self: *Self, item: T) !void {
if (self.tamanho >= self.capacidade) {
try self.crescer();
}
self.items.ptr[self.tamanho] = item;
self.tamanho += 1;
self.items.len = self.tamanho;
}
pub fn obter(self: *const Self, indice: usize) ?T {
if (indice >= self.tamanho) return null;
return self.items[indice];
}
fn crescer(self: *Self) !void {
const nova_cap = if (self.capacidade == 0) 4 else self.capacidade * 2;
if (self.capacidade > 0) {
const novo = try self.allocator.realloc(self.items.ptr[0..self.capacidade], nova_cap);
self.items = novo[0..self.tamanho];
} else {
const novo = try self.allocator.alloc(T, nova_cap);
self.items = novo[0..0];
}
self.capacidade = nova_cap;
}
};
}
test "Lista — operações básicas" {
// testing.allocator detecta memory leaks automaticamente!
var lista = Lista(i32).init(testing.allocator);
defer lista.deinit(); // Sem defer, o teste FALHA por leak
try lista.adicionar(10);
try lista.adicionar(20);
try lista.adicionar(30);
try testing.expectEqual(@as(usize, 3), lista.tamanho);
try testing.expectEqual(@as(?i32, 10), lista.obter(0));
try testing.expectEqual(@as(?i32, 20), lista.obter(1));
try testing.expectEqual(@as(?i32, 30), lista.obter(2));
try testing.expectEqual(@as(?i32, null), lista.obter(3));
}
test "Lista — crescimento dinâmico" {
var lista = Lista(u8).init(testing.allocator);
defer lista.deinit();
// Adiciona mais que a capacidade inicial (4)
for (0..100) |i| {
try lista.adicionar(@intCast(i));
}
try testing.expectEqual(@as(usize, 100), lista.tamanho);
try testing.expectEqual(@as(?u8, 0), lista.obter(0));
try testing.expectEqual(@as(?u8, 99), lista.obter(99));
}
Exemplo 3: Testes de Erro e Floats
const std = @import("std");
const testing = std.testing;
const ErroMatematico = error{
DivisaoPorZero,
Overflow,
RaizNegativa,
};
fn dividir(a: f64, b: f64) ErroMatematico!f64 {
if (b == 0.0) return ErroMatematico.DivisaoPorZero;
return a / b;
}
fn raizQuadrada(x: f64) ErroMatematico!f64 {
if (x < 0.0) return ErroMatematico.RaizNegativa;
return @sqrt(x);
}
test "divisão normal" {
const resultado = try dividir(10.0, 3.0);
try testing.expectApproxEqAbs(3.333333, resultado, 0.001);
}
test "divisão por zero retorna erro" {
try testing.expectError(ErroMatematico.DivisaoPorZero, dividir(5.0, 0.0));
}
test "raiz quadrada de positivo" {
const resultado = try raizQuadrada(16.0);
try testing.expectEqual(@as(f64, 4.0), resultado);
}
test "raiz quadrada de negativo retorna erro" {
try testing.expectError(ErroMatematico.RaizNegativa, raizQuadrada(-1.0));
}
test "comparação de floats com tolerância relativa" {
const pi = 3.14159265358979;
const pi_aprox = 3.14;
try testing.expectApproxEqRel(pi, pi_aprox, 0.001);
}
test "comparação de slices" {
const esperado = [_]i32{ 1, 2, 3, 4, 5 };
var resultado = [_]i32{ 5, 4, 3, 2, 1 };
std.mem.sort(i32, &resultado, {}, std.sort.asc(i32));
try testing.expectEqualSlices(i32, &esperado, &resultado);
}
Exemplo 4: Organizando Testes em Módulos
const std = @import("std");
const testing = std.testing;
/// Módulo de utilidades de string.
const StringUtils = struct {
/// Conta ocorrências de um caractere.
pub fn contarCaractere(s: []const u8, c: u8) usize {
var count: usize = 0;
for (s) |ch| {
if (ch == c) count += 1;
}
return count;
}
/// Verifica se string contém apenas dígitos.
pub fn apenasDigitos(s: []const u8) bool {
if (s.len == 0) return false;
for (s) |c| {
if (c < '0' or c > '9') return false;
}
return true;
}
/// Capitaliza primeira letra.
pub fn capitalizar(buf: []u8) void {
if (buf.len > 0 and buf[0] >= 'a' and buf[0] <= 'z') {
buf[0] -= 32;
}
}
};
// Importa testes de submódulos
test {
// Referencia todos os testes em arquivos importados
_ = StringUtils;
}
test "contarCaractere" {
try testing.expectEqual(@as(usize, 3), StringUtils.contarCaractere("banana", 'a'));
try testing.expectEqual(@as(usize, 0), StringUtils.contarCaractere("xyz", 'a'));
try testing.expectEqual(@as(usize, 0), StringUtils.contarCaractere("", 'a'));
}
test "apenasDigitos" {
try testing.expect(StringUtils.apenasDigitos("12345"));
try testing.expect(!StringUtils.apenasDigitos("123a5"));
try testing.expect(!StringUtils.apenasDigitos(""));
}
test "capitalizar" {
var buf = "zig".*;
StringUtils.capitalizar(&buf);
try testing.expectEqualStrings("Zig", &buf);
var buf2 = "Zig".*;
StringUtils.capitalizar(&buf2);
try testing.expectEqualStrings("Zig", &buf2);
}
Executando Testes
# Executar todos os testes do arquivo
zig test meu_arquivo.zig
# Com saída detalhada
zig test meu_arquivo.zig --summary all
# Filtrar testes por nome
zig test meu_arquivo.zig --test-filter "fatorial"
Módulos Relacionados
- std.debug — Assertions e depuração
- std.log — Logging estruturado
- std.mem — Operações de memória
- std.heap — Allocators