Testes unitarios em AdvPL/TLPP
Como estruturar testes unitarios em codigo Protheus. tlpp-test, mocks de tabela, assertions, integracao com CI/CD.
Testes unitarios em AdvPL nunca foram cultura forte na comunidade — em parte porque a linguagem nao tinha framework nativo. TLPP mudou isso com tlpp-test. Hoje da pra fazer TDD de verdade.
Pre-requisitos
- Protheus 12.1.220x+ com suporte TLPP
- RPO com framework tlpp-test (inclui release moderna)
- Ambiente de homologacao isolado (NAO rode testes em prod!)
Estrutura tipica
#include "tlpp-core.th"
@TestFixture()
class MeuTeste
@Test()
method testSomaSimples()
local nResult := 2 + 2
tlpp.test.assertEquals(4, nResult)
endmethod
@Test()
method testStringNaoVazia()
local cVal := "teste"
tlpp.test.assertTrue(!Empty(cVal))
endmethod
endclass
Asserts disponiveis
tlpp.test.assertEquals(expected, actual)
tlpp.test.assertTrue(condicao)
tlpp.test.assertFalse(condicao)
tlpp.test.assertNull(x)
tlpp.test.assertNotNull(x)
tlpp.test.fail("motivo")
Setup/Teardown
@TestFixture()
class TesteComSetup
@BeforeAll()
method setupOnce()
// Roda uma vez antes de todos os testes
FwLogger():Info("Iniciando suite")
endmethod
@BeforeEach()
method setupCadaTeste()
// Antes de cada @Test
// Limpar dados de teste, abrir conexao, etc
endmethod
@AfterEach()
method cleanup()
// Apos cada @Test
endmethod
@AfterAll()
method tearDownOnce()
// Uma vez no fim
endmethod
endclass
Mockando tabelas Protheus
Aqui esta o desafio: testes de codigo que acessa SC5/SE1/SA1 precisam de dados. Tres abordagens:
Abordagem 1: Base de teste dedicada
Ter um environment de testes com dados conhecidos (fixtures). Resetar antes de cada suite.
Abordagem 2: Mocks via tabela Z
// Em vez de SC5->C5_NUM, leia via helper
function GetNumPed()
return SC5->C5_NUM
endfunction
// No teste, substitua o helper:
@Test()
method testGravaPedido()
// Mock — define que GetNumPed retorna "FAKE001"
setMock("GetNumPed", "FAKE001")
local cNum := GetNumPed()
tlpp.test.assertEquals("FAKE001", cNum)
endmethod
Abordagem 3: Test fixtures via SQL direct
@BeforeEach()
method setupFixtures()
TCSqlExec("DELETE FROM " + RetSqlName("SA1") + " WHERE A1_COD LIKE 'TEST%'")
TCSqlExec("INSERT INTO " + RetSqlName("SA1") + " (A1_FILIAL, A1_COD, A1_LOJA, A1_NOME, D_E_L_E_T_, R_E_C_N_O_) " + ;
"VALUES ('01', 'TEST01', '01', 'CLIENTE TESTE', ' ', 999999)")
endmethod
Rodar os testes
// Manual em sessao Protheus:
tlpp.test.run("MeuTeste")
// Saida tipica:
// > MeuTeste.testSomaSimples ... OK (0.001s)
// > MeuTeste.testStringNaoVazia ... OK (0.000s)
// 2 tests, 2 passed, 0 failed
Integracao com CI/CD
# GitHub Actions
- name: Run TLPP tests
run: |
# Conecta no AppServer de teste e dispara suite
advpls test --suite MeuTeste --reporter junit > test-results.xml
- uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results.xml
Padroes de teste
| Padrao | Quando aplicar |
|---|---|
| AAA (Arrange, Act, Assert) | Sempre — estrutura padrao |
| Given-When-Then | BDD — comportamento esperado |
| Builder pattern | Construir dados de teste complexos |
| Mock external | HTTP, banco externo — nao acessar real |
Pegadinhas
- Testes mexem em base real = corrupcao iminente. SEMPRE em ambiente isolado.
- Performance: testes que mexem em SX2/SX3 sao lentos. Use abordagem de mocks quando possivel.
- Sessao Protheus pra rodar: nao da pra rodar em maquina dev sem AppServer.
- Cobertura: AdvPL nao tem ferramenta nativa de coverage. Use observabilidade (FwLogger) pra entender o que foi exercitado.
Recomendacoes
- Comece com helpers puros (sem tabela) — mais facil testar
- Use TDD pra novas regras de calculo (impostos, comissoes, descontos)
- Mantenha tests/ separado em arvore git
- Rodar suite a cada commit antes de merge