Capítulo 32 5 min de leitura

Testes e benchmarks

Um programa que você não consegue testar é um programa que você não consegue mudar com confiança. O Glide integra testes à própria cadeia de ferramentas — sem framework para instalar, sem configuração. Você escreve funções prefixadas com test_ em arquivos *_test.glide e executa glide test.

Seu primeiro teste

Os testes ficam ao lado do seu código (normalmente em um diretório tests/) em arquivos terminados em _test.glide. Um teste é uma função cujo nome começa com test_, retorna i32 e retorna 0 para indicar aprovação:

tests/math_test.glide
import stdlib::testing::*;
import stdlib::math::*;

fn test_ipow_basic() -> i32 {
    assert_eq!(ipow(2, 10), 1024);
    return 0;
}

fn test_gcd() -> i32 {
    assert_eq!(gcd(48, 36), 12);
    return 0;
}

Execute-os:

shell
glide test tests/math_test.glide
# running tests/math_test.glide ( 2 tests )
#
# 2 passed, 0 failed

Cada arquivo de teste é compilado em seu próprio binário e executado de forma independente, portanto o estado global nunca vaza entre arquivos. Uma asserção que falha marca o teste como reprovado e imprime o local da falha; o executor sai com código diferente de zero se algo falhar — exatamente o que a CI espera.

Os macros de asserção

stdlib::testing oferece cinco macros. Escolha o mais específico — eles produzem mensagens de falha muito mais claras:

assert!(cond);                  // cond deve ser verdadeiro
assert_not!(cond);              // cond deve ser falso
assert_eq!(a, b);               // igualdade para i32, bool, char
assert_str_eq!(a, b);           // igualdade para strings — use para texto
assert_msg!(cond, "context");   // verificação booleana com mensagem personalizada

Um teste com várias asserções falha na primeira que quebrar:

import stdlib::testing::*;

fn slug(s: string) -> string {
    return s.trim().to_lower().replace(" ", "-");
}

fn test_slug() -> i32 {
    assert_str_eq!(slug("  Hello World  "), "hello-world");
    assert_not!(slug("A B").contains(" "));
    return 0;
}

Executando a suíte

glide test recebe um caminho, ou executa tudo no diretório atual:

shell
glide test                       # todo *_test.glide abaixo do diretório atual
glide test tests/                # todo arquivo de teste em um diretório
glide test tests/math_test.glide # um único arquivo

As três camadas

O executor suporta testes em três granularidades — o mesmo glide test, escopos diferentes:

  • Unitário — uma única função ou método de struct isolado: lógica pura, validação, uma regra de parser. O test_ipow_basic acima é um teste unitário.
  • Integração — vários módulos juntos: fs + os, ou sua biblioteca usando outra. Mesma estrutura, mas exercitando mais partes de uma vez.
  • Golden — um programa completo cuja stdout é comparada com um arquivo .expected gravado. O executor compila o programa, executa-o e compara a saída com o diff.

Os testes golden são executados com uma flag:

shell
glide test --golden tests/golden

Cada programa em tests/golden/ é pareado com um arquivo .expected contendo a saída que ele deve produzir. O executor normaliza CRLF para LF, portanto um .expected escrito com terminações de linha Unix funciona no Windows também. Testes golden são perfeitos para CLIs e geradores de código onde o contrato é a saída.

Benchmarks

Correção é um eixo; velocidade é outro. glide bench executa microbenchmarks declarados como funções prefixadas com bench_ que recebem um *Bencher:

sum_bench.glide
import stdlib::bench::*;

pub fn bench_sum(b: *Bencher) {
    let mut total: i32 = 0;
    for let i: i32 = 0; i < b.n; i++ {
        total = total + i;
    }
    b.consume(&total);     // mantém `total` vivo para que o loop não seja otimizado pelo compilador
}

Execute-o:

shell
glide bench sum_bench.glide

Dois aspectos tornam um benchmark honesto:

  • `b.n` é a contagem de iterações, e o executor ajusta automaticamente esse valor — ele escala n para cima até que o benchmark rode por tempo suficiente para ser cronometrado com confiança, depois reporta nanossegundos por operação. Seu loop deve executar exatamente b.n vezes.
  • `b.consume(&x)` informa ao otimizador que o resultado é observado. Sem isso, uma build -O2 perceberia que total nunca é usado e deletaria o loop inteiro — você estaria cronometrando nada. Passe &x para primitivos, ou o valor diretamente para tipos no formato de ponteiro (b.consume(my_vector)).

Recapitulando

O que vem a seguir

Você já sabe compilar, testar e medir programas Glide. O capítulo final — FFI e saídas de emergência — vai na direção oposta: chamando C, incorporando C puro, assembly inline e código específico por plataforma para quando você precisar ir além dos limites da linguagem.