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

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

PadraoQuando aplicar
AAA (Arrange, Act, Assert)Sempre — estrutura padrao
Given-When-ThenBDD — comportamento esperado
Builder patternConstruir dados de teste complexos
Mock externalHTTP, banco externo — nao acessar real

Pegadinhas

Recomendacoes

Veja também