Introdução ao model-based testing para sistemas com estado complexo
1. Fundamentos do Model-Based Testing (MBT)
O Model-Based Testing (MBT) é uma abordagem de teste de software onde casos de teste são gerados automaticamente a partir de modelos formais que descrevem o comportamento esperado do sistema. Diferente dos testes tradicionais — onde o testador manualmente escreve scripts baseados em requisitos — no MBT o foco está na criação e manutenção de um modelo comportamental.
Para sistemas com estado complexo, como plataformas de e-commerce, sistemas de reserva ou jogos, o MBT se destaca porque permite explorar sistematicamente combinações de estados e transições que seriam inviáveis de cobrir manualmente. Enquanto um teste unitário verifica uma função isolada, o MBT verifica sequências de ações que levam o sistema por diferentes estados.
Conceitos-chave:
- Modelo: representação abstrata do comportamento do sistema (ex: máquina de estados finita).
- Estado: configuração específica do sistema em um dado momento.
- Transição: ação ou evento que move o sistema de um estado para outro.
- Caminho de teste: sequência de transições executadas durante um teste.
2. Modelagem de Estados e Transições
A modelagem é o coração do MBT. Utilizamos máquinas de estado finito (FSM) ou diagramas de transição para capturar o comportamento. Para estados complexos, é necessário representar não apenas o estado atual, mas também variáveis de ambiente, histórico de ações e dependências entre componentes.
Exemplo prático: Carrinho de compras
Considere um sistema de carrinho de compras com os seguintes estados:
- vazio
- com_itens
- checkout_iniciado
- pagamento_confirmado
- cancelado
As transições possíveis:
- adicionar_item (vazio → com_itens)
- remover_item (com_itens → vazio, se último item)
- iniciar_checkout (com_itens → checkout_iniciado)
- confirmar_pagamento (checkout_iniciado → pagamento_confirmado)
- cancelar_pedido (checkout_iniciado → cancelado)
Modelamos isso como uma FSM:
Estado: vazio
Transições: adicionar_item -> com_itens
Estado: com_itens
Transições: remover_item -> vazio (se ultimo_item)
iniciar_checkout -> checkout_iniciado
Estado: checkout_iniciado
Transições: confirmar_pagamento -> pagamento_confirmado
cancelar_pedido -> cancelado
Estado: pagamento_confirmado
Transições: (estado final)
Estado: cancelado
Transições: (estado final)
Para capturar a complexidade real, adicionamos variáveis como quantidade_itens e valor_total. O estado com_itens pode ser subdividido em com_um_item e com_multiplos_itens para testar cenários de remoção parcial.
3. Geração Automática de Casos de Teste a Partir do Modelo
Com o modelo definido, utilizamos ferramentas para gerar caminhos de teste automaticamente. As principais estratégias de geração incluem:
- Cobertura de estados: garantir que cada estado seja visitado pelo menos uma vez.
- Cobertura de transições: garantir que cada transição seja executada.
- Caminhos aleatórios: gerar sequências aleatórias para explorar combinações inesperadas.
Ferramentas populares:
- GraphWalker: open-source, suporta modelos em formato JSON/YAML e gera caminhos baseados em cobertura.
- Spec Explorer: ferramenta da Microsoft para modelagem e geração de testes.
- NModel: biblioteca .NET para MBT.
Exemplo de caminho gerado pelo GraphWalker para o carrinho:
Caminho gerado (cobertura de transições):
1. vazio -> adicionar_item -> com_itens
2. com_itens -> adicionar_item -> com_itens
3. com_itens -> remover_item -> com_itens (restam itens)
4. com_itens -> remover_item -> vazio
5. vazio -> adicionar_item -> com_itens
6. com_itens -> iniciar_checkout -> checkout_iniciado
7. checkout_iniciado -> confirmar_pagamento -> pagamento_confirmado
8. checkout_iniciado -> cancelar_pedido -> cancelado
Para evitar explosão combinatória, filtramos caminhos inviáveis (ex: tentar remover item de carrinho vazio) e limitamos a profundidade máxima.
4. Estratégias para Lidar com a Complexidade de Estado
Sistemas reais podem ter centenas de estados. Três técnicas ajudam a gerenciar essa complexidade:
Particionamento de estados: Divida o modelo em submodelos. Ex: um modelo para o fluxo de compra, outro para o fluxo de devolução.
Abstração: Simplifique estados agrupando configurações similares. Ex: em vez de modelar cada produto individualmente, use categorias (produto_barato, produto_caro).
Redução: Identifique estados equivalentes (ex: dois estados que levam às mesmas transições) e transições redundantes. Pode caminhos que nunca são executados no sistema real.
Exemplo de abstração no carrinho:
Estado original: com_3_itens_total_150
Estado abstraído: com_itens_valor_alto
5. Integração do MBT com o Pipeline de Testes
Os caminhos gerados precisam ser executados contra o sistema real. A integração típica envolve:
- Gerar caminhos de teste a partir do modelo.
- Converter cada caminho em um script de teste no framework alvo (pytest, JUnit, Robot Framework).
- Executar os scripts, verificando pré-condições (estado atual do sistema) e pós-condições (estado esperado após a transição).
Exemplo de executor em Python (pytest):
# executor_mbt.py
import pytest
from sistema_carrinho import Carrinho
caminhos_teste = [
["adicionar_item", "adicionar_item", "iniciar_checkout"],
["adicionar_item", "remover_item"],
]
@pytest.mark.parametrize("caminho", caminhos_teste)
def test_caminho_carrinho(caminho):
carrinho = Carrinho() # estado inicial: vazio
for acao in caminho:
if acao == "adicionar_item":
carrinho.adicionar(produto="livro", preco=50)
assert carrinho.estado in ["com_itens"]
elif acao == "remover_item":
carrinho.remover("livro")
elif acao == "iniciar_checkout":
carrinho.checkout()
assert carrinho.estado == "checkout_iniciado"
print(f"Caminho {caminho} executado com sucesso")
A sincronização entre modelo e sistema real é crítica: o modelo assume que certas pré-condições são verdadeiras; o executor deve verificar isso antes de cada ação.
6. Validação e Manutenção do Modelo
Um modelo impreciso gera testes falsos. Para garantir fidelidade:
- Validação contínua: compare o comportamento do modelo com o sistema real usando testes de fumaça.
- Evolução do modelo: quando o sistema muda (ex: novo estado "reserva_pendente"), atualize o modelo antes de gerar novos testes.
- Métricas de qualidade: meça a cobertura de estados alcançada pelos testes gerados vs. a cobertura esperada. Um modelo com 80% de cobertura de estados pode indicar caminhos não explorados.
7. Casos de Uso e Limitações do MBT
Quando usar MBT:
- Sistemas com máquina de estados explícita (ex: protocolos de rede, controle de processos).
- Fluxos de trabalho complexos com múltiplas ramificações (ex: sistemas de aprovação).
- Regras de negócio que dependem de histórico de ações.
Desafios comuns:
- Explosão de estados: modelos grandes geram milhões de caminhos. Solução: particionamento e abstração.
- Modelos imprecisos: exigem manutenção constante.
- Custo inicial: modelar um sistema complexo pode levar semanas.
Comparação com outras abordagens:
- Testes baseados em propriedades (ex: Hypothesis): focam em entradas, não em sequências de estados.
- Fuzzing: gera entradas aleatórias, sem modelo comportamental.
- Testes exploratórios: dependem da intuição humana, não de cobertura sistemática.
O MBT brilha onde a ordem das ações importa — exatamente o caso de sistemas com estado complexo.
Referências
- GraphWalker Documentation — Documentação oficial da ferramenta open-source para geração de caminhos de teste a partir de modelos.
- Spec Explorer by Microsoft Research — Ferramenta para modelagem e teste baseado em modelos, com exemplos práticos.
- Model-Based Testing in Practice (IEEE) — Artigo técnico sobre aplicação de MBT em sistemas industriais com estado complexo.
- NModel: A Model-Based Testing Library for .NET — Biblioteca open-source para MBT em .NET, com tutoriais e exemplos de modelagem.
- Introduction to Model-Based Testing (Tutorial da ISTQB) — Guia introdutório da ISTQB sobre conceitos e práticas de MBT.