Voltar ao Blog Gestão Empresarial

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

Luis Felipe Miléo

· 6 min de leitura

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:

  1. Documentação legível por não-programadores
  2. Teste automatizado que roda em CI
  3. 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:

  1. Receita Federal — cruzamento eSocial × DCTFWeb × DARFs INSS/IRRF
  2. Auditoria trabalhista — TST, MPT, processos individuais
  3. 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.leavehr.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.feature mal 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.

#folha #esocial

Compartilhar

Sobre o autor

Luis Felipe Miléo

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 LinkedIn