Salary rules em BDD: como `.feature` torna o cálculo de folha auditável
Como a folha CLT da KMEE usa BDD com arquivos .feature em Gherkin para especificar salary rules, gerando testes legíveis para auditoria fiscal e trabalhista.
Luis Felipe Miléo
Folha de pagamento brasileira tem uma característica desagradável: cada rubrica é uma regra que precisa ser explicada para três públicos diferentes — desenvolvedor, contador e auditor fiscal. Os três falam línguas distintas. O contador escreve em português jurídico (“incide INSS sobre adicional noturno habitual”), o desenvolvedor escreve em Python, e o auditor quer ver a regra aplicada em casos concretos.
A folha CLT da KMEE (PR #277, 232 testes) resolve isso com BDD: cada salary rule complexa tem um arquivo .feature em Gherkin que descreve o comportamento esperado em português, e o mesmo arquivo é executado como teste automatizado. Este post mostra como funciona.
O que é Gherkin
Gherkin é a sintaxe usada por ferramentas como Cucumber, Behave (Python) e Behaviour (Ruby) para escrever especificações executáveis. A estrutura básica é:
Funcionalidade: descricao do que esta sendo testado
Cenario: caso especifico
Dado uma situacao inicial
Quando uma acao acontece
Entao um resultado e esperado
A graça é que o mesmo arquivo serve como:
- Documentação legível por não-programadores
- Teste automatizado que roda em CI
- Especificação contratual entre time técnico e área de negócio
Aplicado a salary rules
Considere a regra de adicional noturno: incide 20% sobre horas trabalhadas entre 22h e 5h, com hora noturna reduzida de 60 para 52 minutos e 30 segundos (art. 73 CLT).
A salary rule em Python:
{
"code": "ADIC_NOT",
"name": "Adicional noturno",
"amount_python_compute": """
horas_noturnas = inputs.HORAS_NOTURNAS.amount
hora_normal = contract.wage / (220 * (52.5/60)) # hora reduzida
result = horas_noturnas * hora_normal * 0.20
""",
}
Ler isso e entender se o cálculo está certo? Difícil. Mas o .feature correspondente:
# odoo/addons/l10n_br_hr_payroll/tests/features/adicional_noturno.feature
Funcionalidade: Adicional noturno do art. 73 CLT
Background:
Dado um colaborador com salario base de R$ 2.200,00
E carga horaria mensal de 220 horas
Cenario: 8 horas trabalhadas em horario noturno
Dado que o colaborador trabalhou 8 horas entre 22h e 5h
Quando a folha mensal e calculada
Entao o adicional noturno deve ser R$ 22,86
E a hora noturna foi computada com reducao para 52,5 minutos
Cenario: hora noturna que se prolonga apos as 5h
Dado que o colaborador trabalhou em jornada que comecou as 22h
E terminou as 6h
Quando a folha mensal e calculada
Entao todas as 8 horas devem ser consideradas noturnas
E o adicional deve ser aplicado a toda a jornada
Cenario: trabalho diurno apenas
Dado que o colaborador trabalhou apenas das 8h as 17h
Quando a folha mensal e calculada
Entao o adicional noturno deve ser R$ 0,00
Um auditor que abre esse arquivo entende imediatamente quais casos foram cobertos. Um contador consegue revisar e apontar caso faltante. Um juiz do trabalho, num processo, tem o cálculo justificado linha a linha.
Estrutura no Odoo
A folha CLT da KMEE organiza assim:
l10n_br_hr_payroll/
├── tests/
│ ├── features/
│ │ ├── adicional_noturno.feature
│ │ ├── horas_extras_50_100.feature
│ │ ├── inss_progressivo.feature
│ │ ├── irrf_dependentes.feature
│ │ ├── decimo_terceiro_proporcional.feature
│ │ ├── ferias_art_130.feature
│ │ ├── rescisao_sem_justa_causa.feature
│ │ └── ...
│ ├── steps/
│ │ ├── colaborador_steps.py
│ │ ├── payslip_steps.py
│ │ └── assertion_steps.py
│ └── test_features.py
Os “steps” em Python implementam cada Dado/Quando/Então mapeando para chamadas da API do Odoo:
# tests/steps/colaborador_steps.py
@given(parsers.parse("um colaborador com salario base de R$ {valor:f}"))
def colaborador_salario(context, valor):
context.colaborador = context.env["hr.employee"].create({
"name": "Teste BDD",
"cpf": "12345678901",
})
context.contrato = context.env["hr.contract"].create({
"employee_id": context.colaborador.id,
"wage": valor,
"state": "open",
})
@when("a folha mensal e calculada")
def calcular_folha(context):
context.payslip = context.env["hr.payslip"].create({
"employee_id": context.colaborador.id,
"contract_id": context.contrato.id,
"date_from": "2026-05-01",
"date_to": "2026-05-31",
})
context.payslip.compute_sheet()
@then(parsers.parse("o adicional noturno deve ser R$ {valor:f}"))
def assert_adicional(context, valor):
linha = context.payslip.line_ids.filtered(lambda l: l.code == "ADIC_NOT")
assert abs(linha.amount - valor) < 0.01, f"Esperado {valor}, obtido {linha.amount}"
Cada step é reutilizável entre features. A primeira pessoa que escreve uma feature paga o custo de criar steps; as próximas reaproveitam.
Por que isso importa para auditoria
Folha brasileira está sujeita a três frentes de auditoria:
- Receita Federal — cruzamento eSocial × DCTFWeb × DARFs INSS/IRRF
- Auditoria trabalhista — TST, MPT, processos individuais
- Auditoria interna — em órgão público federal cliente da KMEE, controles do TCU
Os três cenários têm a mesma pergunta: “por que esse cálculo deu esse valor?” Em sistema sem BDD, a resposta é “está no código”. Em sistema com BDD, a resposta é “este é o cenário aplicável” e mostra o .feature correspondente.
No órgão público cliente, os arquivos .feature são anexados ao manual de procedimentos do RH e atualizados junto com mudanças de legislação. Quando a Lei 14.611/2023 (igualdade salarial) trouxe novas obrigações, novos cenários foram adicionados nos .feature antes de qualquer código ser escrito.
Pirâmide de testes
Os 232 testes do PR #277 se dividem em:
- Unitários — modelos isolados, validações de campo
- Integração — fluxo
hr.leave→hr.payslip→ S-1200 - BDD — cenários de negócio em
.feature
Os BDD ficam no topo: poucos, mas cobrindo casos que justificam decisões legais. Os unitários estão na base e são a maioria. Misturar os três níveis evita tanto over-testing (testar o framework Odoo) quanto under-testing (deixar regra fiscal sem cobertura).
Limitações
BDD não é mágica. Pontos a considerar:
- Custo de manutenção —
.featuremal escrito vira chatter ilegível. - Lentidão — testes BDD são lentos por natureza (rodam Odoo inteiro). Não substituem unitários rápidos.
- Rigidez — refactor que não muda comportamento ainda quebra steps que olham implementação.
A folha CLT da KMEE usa BDD apenas para regras com complexidade legal (CLT, IN da Receita, leiautes eSocial). Regras simples ficam só com unitário.
Como executar
No ambiente Doodba do Odoo 16:
invoke test --modules l10n_br_hr_payroll --tag bdd
Saída típica:
Funcionalidade: Adicional noturno do art. 73 CLT
Cenario: 8 horas trabalhadas em horario noturno . . . PASS
Cenario: hora noturna que se prolonga apos as 5h . . . PASS
Cenario: trabalho diurno apenas . . . PASS
3 cenarios (3 passed)
Se um cenário falha, a saída mostra qual passo (Dado/Quando/Então) quebrou e o valor esperado vs. obtido.
Conclusão
BDD em .feature torna a folha CLT auditável por humanos não-técnicos. A folha CLT da KMEE usa essa abordagem em produção há anos no órgão público federal cliente, e está sendo doada à OCA com toda a suite no PR #277. É uma prática que não vemos em folhas comerciais (Senior, ADP, TOTVS) e que diferencia o Odoo quando o cliente precisa justificar cálculo para fiscal.
Veja folha de pagamento Odoo, eSocial Odoo e Odoo vs Senior.
Sobre o autor
Luis Felipe Miléo
Desenvolvedor Odoo · KMEE
Desenvolvedor especializado em localização fiscal e projetos open source no ecossistema Odoo/OCA, com foco em integrações para o mercado latino-americano.
Ver perfil no LinkedInArtigos relacionados
Open Finance regulado vs APIs proprietárias: qual usar para cada caso
7 de jul. de 2026
Gestão EmpresarialDDA no Odoo: contas a pagar 100% automatizadas
30 de jun. de 2026
Gestão EmpresarialTOTVS está descontinuando sua API bancária — Odoo é a alternativa neutra
9 de jun. de 2026