Estruturando Blocos Given/When/Then e Ciclo de Vida
Depois de concluirmos esta aula, nós:
- Saberemos identificar os blocos construtivos de uma especificação de funcionalidade (feature specification) no Kotest 6.
- Saberemos como estruturar cenários com efeitos colaterais usando os blocos
Given,When,TheneAndnoBehaviorSpec. - Saberemos quando optar por blocos diretos (equivalentes ao bloco
expect:do Spock) para especificar métodos puramente funcionais. - Saberemos como garantir a liberação de recursos alocados por um teste mesmo quando uma exceção é lançada (usando
autoCloseouafterTest).
1. A Estrutura de uma Especificação de Funcionalidade
Um bloco de especificação descreve o comportamento esperado de uma funcionalidade (feature) implementada pelo sistema sob especificação. Tanto no Spock quanto no Kotest, nomeamos nossos cenários usando literais String com frases completas em linguagem natural — uma enorme vantagem de legibilidade para documentar regras de negócio.
Em uma especificação BDD completa, dividimos o cenário em blocos semânticos bem definidos:
- Bloco de Preparação (
Given/setup): é o primeiro bloco do cenário e contém a configuração do estado inicial da funcionalidade descrita. - Blocos de Estímulo e Resposta (
WheneThen): o blocoWhendescreve a ação/estímulo executado no sistema sob teste, e o blocoThendescreve a resposta ou mudança de estado esperada. Um mesmoGivenpode conter múltiplos paresWhen/Thenpara testar diferentes estímulos sobre o mesmo estado inicial! - Bloco de Expectativa Direta (
should/it— equivalente aoexpect): descreve o estímulo e a resposta esperada em uma única expressão concisa, ideal para funções puras sem efeitos colaterais. - Limpeza de Recursos (
autoClose/afterEach— equivalente aocleanup): usado para liberar recursos (arquivos temporários, conexões, streams) abertos pelo teste, com garantia de execução mesmo se o teste lançar uma exceção. - Bloco de Dados (
withData— equivalente aowhere): usado para escrever testes orientados a dados (data-driven tests), que estudaremos em detalhes na Aula 06 deste tópico.
O diagrama conceitual abaixo resume essa estrutura no BehaviorSpec do Kotest:
|
|
Vamos colocar esses blocos em prática especificando o comportamento do método get() da classe HashMap!
2. Escrevendo Especificações de Funcionalidade Passo a Passo
2.1. Usando o Bloco de Preparação (Given)
O bloco de preparação é o ponto de partida da nossa especificação e serve para configurar o objeto que será testado.
Em um estilo livre como ShouldSpec ou FunSpec, você pode simplesmente escrever o código de preparação implicitamente nas primeiras linhas do teste:
|
|
Contudo, quando queremos deixar o formato Given-When-Then explícito no código e nos relatórios de teste, utilizamos o BehaviorSpec e abrimos um bloco Given descrevendo a pré-condição com uma String:
|
|
Depois de configurarmos a pré-condição no Given, precisamos especificar o comportamento esperado. Vamos fazer isso com os blocos When e Then.
2.2. Usando os Blocos When e Then
Especificamos o comportamento esperado combinando os blocos When (que executa o estímulo) e Then (que verifica o resultado esperado).
Vamos adicionar nosso primeiro par When / Then dentro do bloco Given para verificar que o método get() do HashMap retorna o valor associado quando a chave existe:
|
|
Como vimos antes, um mesmo contexto Given pode ter múltiplos blocos When e Then. Vamos adicionar um segundo bloco When / Then que verifica que o método get() do HashMap retorna null quando nenhum valor é encontrado para a chave informada (incorrectKey):
|
|
[!TIP] No
BehaviorSpecdo Kotest 6, você também pode usar o bloco intermediárioAnd("...")tanto após umGivenquanto após umWhenouThencaso queira detalhar múltiplas pré-condições ou verificações encadeadas!
Ao observar o código acima, notamos que criar uma variável intermediária val found = map[key] dentro de um bloco When apenas para ler um valor sem efeito colateral parece um pouco artificial. Vamos ver como simplificar isso!
2.3. Usando Blocos de Expectativa Direta (Equivalente ao expect: e and:)
Quando queremos descrever o estímulo e a resposta esperada em uma única expressão concisa (como fazíamos com os blocos expect: e and: no Spock), o estilo ShouldSpec (ou DescribeSpec) do Kotest permite escrever cada expectativa diretamente dentro de um bloco should("...") agrupado sob um context("..."):
|
|
Fica claro que podemos especificar o comportamento esperado tanto com When/Then (BehaviorSpec) quanto com expectativas diretas (ShouldSpec / DescribeSpec). Como escolher a ferramenta certa para cada situação?
A diretriz clássica de BDD responde perfeitamente a essa dúvida:
Regra prática: Use blocos
When-Thenpara descrever métodos que possuem efeitos colaterais (mutações de estado, salvamento em banco, envio de mensagens) e use expectativas diretas (should/expect) para descrever métodos puramente funcionais (cálculos e consultas sem mutação).
2.4. Limpando Recursos com Garantia de Execução (autoClose e try / finally)
Às vezes, nosso teste aloca recursos externos (como criar um arquivo no disco, abrir um InputStream ou iniciar um cliente HTTP) que precisam obrigatoriamente ser liberados ao final do teste, mesmo se uma asserção falhar ou uma exceção for lançada no meio do caminho.
No Spock, usávamos o rótulo cleanup:. No Kotest 6, temos duas maneiras idiomáticas de garantir essa limpeza:
- Para recursos que implementam
AutoCloseable/Closeable: usamos a função utilitáriaautoClose(...)nativa do Kotest, que registra o recurso e o fecha automaticamente ao final do ciclo de vida! - Para limpeza arbitrária (como deletar um
Filecriado no teste): usamosafterEach/afterTestou um blocotry { ... } finally { file.delete() }(ou ainda a funçãotempfile()nativa doio.kotest.engine.spec.tempfileque já agenda a remoção automática!).
Vamos ver o exemplo equivalente ao FileSpec que cria um arquivo /tmp/foo.txt, verifica que ele existe e é um arquivo regular, e garante sua remoção no bloco finally (mesmo que o teste lance uma exceção):
|
|
[!IMPORTANT] Assim como no bloco
cleanup:do Spock, sempre escreva sua lógica de limpeza de forma defensiva (verificando se o recurso foi inicializado ou usando chamadas seguras?.), pois uma exceção pode ter sido lançada antes mesmo de o recurso ter sido totalmente criado.
Resumo
Nesta aula, aprendemos seis pontos essenciais:
- Uma especificação de funcionalidade é estruturada em blocos semânticos.
- O bloco
Given(oucontext) configura o estado inicial da funcionalidade descrita. - Os blocos
WheneThendescrevem o estímulo executado e a resposta esperada, sendo ideais para métodos com efeitos colaterais. - Expectativas diretas (
should/it) descrevem o estímulo e a resposta em uma única expressão, sendo ideais para métodos puramente funcionais. - Podemos garantir a limpeza de recursos alocados no teste usando
autoClose(),tempfile()ou blocostry / finallyeafterEach. - Podemos encadear ou subdividir etapas dentro de um cenário usando blocos
Andou múltiplos blocosWhen/Then.