Testes unitários e mocking
O NeoObjectPascal traz um framework de testes nativo — não é uma biblioteca externa, é parte da própria linguagem. Você escreve testes com a palavra-chave test, valida resultados com expect(...) e executa tudo diretamente pelo interpretador, com relatório de resultados e cobertura.
Este guia cobre a escrita de testes, todos os matchers disponíveis, a organização e execução de suítes e o sistema de mocking para isolar dependências.
Escrevendo um teste
Um teste é uma declaração de topo, no mesmo nível de funções e classes. A forma é:
test "descrição do teste"
begin
// corpo do teste
end;A descrição é uma string livre que aparece no relatório. Dentro do bloco begin ... end você escreve código normal: declara variáveis, chama funções, cria objetos e, ao final, faz as verificações com expect.
class Calculadora
public function somar(a: Integer, b: Integer): Integer
begin
return a + b;
end;
end;
test "Calculadora deve somar dois números corretamente"
begin
// GIVEN - dado uma calculadora
var calc: Calculadora;
calc := new Calculadora();
// WHEN - quando somamos 5 + 3
var resultado: Integer;
resultado := calc.somar(5, 3);
// THEN - então o resultado deve ser 8
expect(resultado).toBe(8);
end;Padrão GIVEN-WHEN-THEN
Organizar cada teste em três blocos — GIVEN (preparação), WHEN (ação) e THEN (verificação) — deixa a intenção clara. É o mesmo que Arrange-Act-Assert. Use comentários // para marcar cada fase.
Matchers (expect)
A verificação sempre começa com expect(valorReal) seguido de um matcher que descreve a expectativa. Se a expectativa falhar, o teste é marcado como [FAIL] com uma mensagem; caso contrário, [PASS].
.toBe(esperado)
Verifica igualdade exata. É o matcher mais usado, serve para números, strings e booleanos.
test "toBe compara valores exatos"
begin
expect(2 + 2).toBe(4);
expect("Olá" + " mundo").toBe("Olá mundo");
end;.toEqual(esperado)
Equivalente a .toBe para igualdade de valores — use quando quiser deixar explícito no texto que se trata de uma comparação de igualdade estrutural.
test "toEqual verifica igualdade"
begin
var total: Integer;
total := 100 + 50;
expect(total).toEqual(150);
end;.toBeTrue() e .toBeFalse()
Verificam valores booleanos sem precisar comparar com true/false explicitamente.
test "matchers booleanos"
begin
var maiorDeIdade: Boolean;
maiorDeIdade := 20 >= 18;
expect(maiorDeIdade).toBeTrue();
expect(10 > 100).toBeFalse();
end;.toBeNull()
Verifica se um valor é nulo — útil para campos ainda não inicializados ou retornos ausentes.
test "campo não inicializado começa nulo"
begin
var resultado: Object;
expect(resultado).toBeNull();
end;| Matcher | Verifica |
|---|---|
.toBe(v) | igualdade exata |
.toEqual(v) | igualdade de valor |
.toBeTrue() | valor é verdadeiro |
.toBeFalse() | valor é falso |
.toBeNull() | valor é nulo |
Organizando os testes
- Extensão
.test.npas— arquivos de teste usam o sufixo.test.npas(por exemplo,calculadora.test.npas). É essa convenção que o executor de testes reconhece. - Um comportamento por teste — cada
testdeve verificar um único comportamento. Prefira vários testes pequenos e bem nomeados a um teste grande que valida tudo. - Testes independentes — cada teste deve criar suas próprias instâncias e não depender da ordem de execução.
Uma organização típica separa código-fonte de testes:
projeto/
├── src/
│ ├── calculadora.npas
│ └── conta.npas
└── tests/
├── calculadora.test.npas
└── conta.test.npasExecutando os testes
Um único arquivo com -t
Para rodar um arquivo de teste isolado, use a flag -t (ou --test):
java -jar neoobjectpascal.jar -t calculadora.test.npas========== TEST RESULTS ========== [PASS] Calculadora deve somar dois números corretamente [PASS] Calculadora deve subtrair dois números corretamente [PASS] Calculadora deve multiplicar dois números corretamente ================================== Total: 3 | Passed: 3 | Failed: 0
Quando um teste falha, o relatório mostra a descrição e a diferença encontrada:
========== TEST RESULTS ========== [PASS] Calculadora deve somar dois números corretamente [FAIL] Calculadora deve multiplicar dois números corretamente: esperado 42 mas obteve 40 ================================== Total: 3 | Passed: 2 | Failed: 1
Todos os testes com --test-all
Para rodar uma suíte inteira de forma recursiva, apontando para um diretório, use --test-all (ou -ta). Ele encontra todos os arquivos .test.npas abaixo do diretório e ainda apresenta um resumo de cobertura. A cobertura é medida pelos métodos públicos de classe exercitados pelos testes — cada método público de uma classe conta, e o relatório lista os métodos cobertos e não cobertos por classe. Não é uma razão entre arquivos de teste e arquivos de código:
java -jar neoobjectpascal.jar --test-all ./tests========== TEST RESULTS ========== [PASS] Calculadora deve somar dois números corretamente [PASS] ContaBancaria deve inicializar com saldo correto [PASS] ContaBancaria deve depositar valor corretamente [PASS] Email válido deve passar na validação ================================== Total: 4 | Passed: 4 | Failed: 0Test Coverage (public class methods): Public methods: 3 Covered: 3 Coverage: 100.00% ✓ Calculadora.somar ✓ ContaBancaria.depositar ✓ ValidadorEmail.validar
Meta de cobertura
Busque manter a cobertura em 80% ou mais dos métodos públicos das suas classes. Cada método público não exercitado por nenhum teste aparece como não coberto no relatório, apontando exatamente onde faltam testes.
Código de saída
O executor retorna código de saída diferente de zero quando há falhas, o que permite usá-lo diretamente em pipelines de CI/CD.
Mocking
Testes unitários devem ser rápidos e previsíveis. Quando a unidade que você está testando depende de algo externo — um serviço de e-mail, uma API, um banco de dados — você não quer disparar a chamada real. É aí que entra o mocking: você substitui uma função ou método por uma versão que devolve um valor fixo, e depois verifica que ela foi chamada.
Sintaxe
// Substitui uma função ou método por um retorno fixo:
mock calcularDesconto thenReturn 50;
mock EmailService enviarEmail thenReturn true;
// Verifica que a função/método mockado foi chamado:
verify calcularDesconto;
verify EmailService enviarEmail;mock Alvo metodo thenReturn valor;— para métodos de classe, informe a classe e o método. Para funções isoladas, informe apenas o nome. A forma com ponto (mock EmailService.enviarEmail thenReturn true;) também é aceita.verify Alvo metodo;— falha o teste se o alvo mockado nunca tiver sido chamado.
A convenção é declarar os mocks no bloco GIVEN e fazer os verify no bloco THEN.
Exemplo: isolando um serviço de e-mail
Considere um NotificadorUsuario que depende de um EmailService. No teste, não queremos enviar e-mail de verdade — apenas garantir que o notificador delega a chamada corretamente e retorna sucesso.
// Serviço externo que queremos mockar
class EmailService
public function enviarEmail(destinatario: String, mensagem: String): Boolean
begin
// Em produção, enviaria e-mail real via SMTP
WriteLn("Enviando email para ", destinatario);
return true;
end;
end;
// Classe que usa o serviço
class NotificadorUsuario
var emailService: EmailService;
constructor Create(service: EmailService)
begin
self.emailService := service;
end;
public function notificar(usuario: String): Boolean
begin
return self.emailService.enviarEmail(usuario, "Você tem uma nova notificação!");
end;
end;
test "NotificadorUsuario deve chamar EmailService ao notificar"
begin
// GIVEN - dado um serviço de e-mail mockado
var emailService: EmailService;
emailService := new EmailService();
// Mock do método enviarEmail para retornar true sem enviar e-mail real
mock EmailService enviarEmail thenReturn true;
var notificador: NotificadorUsuario;
notificador := new NotificadorUsuario(emailService);
// WHEN - quando notificamos um usuário
var resultado: Boolean;
resultado := notificador.notificar("usuario@teste.com");
// THEN - então deve retornar true
expect(resultado).toBeTrue();
// E o método enviarEmail deve ter sido chamado
verify EmailService enviarEmail;
end;Exemplo: mockando uma função isolada
O mesmo mecanismo funciona para funções de topo. Aqui substituímos um cálculo por um valor fixo:
function calcularDesconto(valor: Integer): Integer
begin
// Cálculo complexo que queremos evitar no teste
return valor * 10;
end;
test "Deve usar função mockada em vez da real"
begin
// GIVEN - dada uma função mockada
mock calcularDesconto thenReturn 50;
// WHEN - quando chamamos a função
var desconto: Integer;
desconto := calcularDesconto(100);
// THEN - então deve retornar o valor mockado
expect(desconto).toBe(50);
// E a função deve ter sido chamada
verify calcularDesconto;
end;Escopo do mocking
O mocking atual substitui o retorno completo do alvo e verifica se ele foi chamado. Ainda não há correspondência por parâmetros específicos, contagem de chamadas nem verificação de ordem. Mocke apenas as dependências externas e mantenha a lógica interna sob teste real.
Próximos passos
Com testes automatizados no lugar, o passo natural é aprender a investigar comportamentos em tempo de execução. Continue em Debugger, VS Code e execução na nuvem.
Para revisar a modelagem de classes usada nos exemplos, veja Classes e objetos. Uma coletânea de programas completos está em Exemplos.