Testar o domínio
Dado estes fatos, quando este comando, então estes fatos — ou esta recusa.
Com decidir puro, o teste do domínio não precisa de banco, de agregado montado nem de dublê: dado o histórico, quando o comando chega, então saem estes eventos — ou esta recusa. O teste lê como a regra, e a regra é o que ele confere.
dataforge
adopt Arcane.Dominio as D
// evoluir: o estado depois de um fato. Pura, e nunca recusa nada.
aplicar := {
"ContaAberta": lambda s, d: {"titular": d["titular"], "saldo": 0, "aberta": yes},
"Depositado": lambda s, d: {...s, "saldo": s["saldo"] + d["valor"]},
"Sacado": lambda s, d: {...s, "saldo": s["saldo"] - d["valor"]},
"ContaEncerrada": lambda s, d: {...s, "aberta": no}
}
// decidir: os fatos que um comando produz — ou o motivo da recusa.
action decidir(estado, comando):
tipo := comando["tipo"]
given tipo is "abrir":
yield [{"nome": "ContaAberta", "dados": {"titular": comando["titular"]}}]
given not (estado["aberta"] ?? no):
trigger "a conta não está aberta"
given tipo is "depositar":
yield [{"nome": "Depositado", "dados": {"valor": comando["valor"]}}]
given tipo is "sacar":
given comando["valor"] bigger estado["saldo"]:
trigger $"saldo {estado["saldo"]} não cobre {comando["valor"]}"
yield [{"nome": "Sacado", "dados": {"valor": comando["valor"]}}]
trigger $"comando desconhecido: {tipo}"
action dado_quando_entao(historia, comando):
yield decidir(D.reconstituir(historia, aplicar), comando)
aberta := [{"nome": "ContaAberta", "dados": {"titular": "Ana"}},
{"nome": "Depositado", "dados": {"valor": 50}}]
// então: o fato certo
assert dado_quando_entao(aberta, {"tipo": "sacar", "valor": 20}) is
[{"nome": "Sacado", "dados": {"valor": 20}}]
// então: a recusa certa, com o motivo
motivo := void
monitor:
dado_quando_entao(aberta, {"tipo": "sacar", "valor": 80})
handle Error as e:
motivo := e.message
assert motivo.contains("não cobre")
// então: nada acontece numa conta encerrada
encerrada := aberta + [{"nome": "ContaEncerrada", "dados": {}}]
monitor:
dado_quando_entao(encerrada, {"tipo": "depositar", "valor": 1})
assert no
handle Error as e:
assert e.message.contains("não está aberta")E as invariantes de um agregado se testam pelo lado de fora: tentar o comando que as violaria, e conferir que o estado e a versao() não mudaram. Para o formato dado/quando/então com relatório por passo, `Crucible.cenario`.