Fixture Methods

São responsáveis por configurar e limpar o ambiente que os métodos de teste (features) rodam.

void setupSpec()    // executa uma vez antes da primeira feature.
void setup()        // executa antes de cada feature.
void cleanup()      // executa após cada feature.
void cleanupSpec()  // executa uma vez após a última feature.

Feature Methods (testes)

Descrevem as features esperadas do que está sendo testado.

void "adicionar um elemento no stack"() {
    // blocks...
}

Blocks

Blocos representam cada fase dentro de um teste.

given:

Faz o setup do cenário de teste.

  • Se usado, deve ser o primeiro bloco.
  • Não pode ser usado mais de uma vez por feature.

when: / then:

  • when: descreve um estímulo (algo é feito).
  • then: descreve uma expectativa dado aquele estímulo, deve conter pelo menos uma condição.

Condições são expressões booleanas como:

then:
!stack.empty
stack.size() == 1
stack.peek() == element
 
// ou
then:
with(stack) {
    !empty
    size() == 1
    peek() == element
}

Também pode ser usado thrown() quando se espera que algo feito no when lance uma exceção:

when:
stack.pop() // Tira um elemento de uma pilha vazia
 
then:
thrown(EmptyStackException)      // Deve lançar essa exceção
notThrown(NullPointerException)  // Não deve lançar essa outra
stack.empty

expect:

Expect geralmente é usado quando a função testada é pura, já when / then quando ocorrem efeitos colaterais.

  • É uma versão curta do when / then .
when:
def x = Math.max(1, 2)
 
then:
x == 2
expect:
Math.max(1, 2) == 2

cleanup:

Executado mesmo se a feature falhar, usado para liberar recursos (geralmente com o ?. ).

where:

Usado para criar DDT (Data-Driven Testing), onde a mesma feature é testada com diferentes dados (geralmente estáticos).

Data Tables
void "Calcular o maximo de dois numeros #a e #b"() {
    expect:
    Math.max(a, b) == c
 
    where:
    a | b || c
    1 | 3 || 3
    7 | 4 || 7
    0 | 0 || 0
}
Data Pipes

Usa << para ligar uma variável a um iterável (listas, ranges, query SQL, geradores), mesma coisa que o data table (geralmente usa dados dinâmicos).

where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]
 
// ou
[a, b, _, c] << sql.rows("select * from maxdata")  // _ ignora um valor

Interaction Based Testing

Técnica que foca no comportamento dos objetos e em como eles interagem (com chamadas de métodos), ao invés de focar apenas no estado deles.

Mocks

Mock objects são criados com Mock() e podem ser usados para verificar interações e fazer stubbing.

Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()

Comportamento padrão:

  • Chamadas de métodos são permitidas mas não fazem nada.
  • Retornam valor padrão do tipo (false, 0, null).
  • equals, hashCode e toString têm comportamento especial (mock é igual apenas a si mesmo).

Declarar interações na criação:

Subscriber subscriber = Mock {
    1 * receive("hello")
    1 * receive("goodbye")
}

Interactions

Descrevem quais invocações de métodos são esperadas.

Cardinality

Quantas vezes a chamada é esperada:

1 * subscriber.receive("hello")
0 * subscriber.receive("hello")
(1..3) * subscriber.receive("hello")
(1.._) * subscriber.receive("hello") // pelo menos uma vez
(_..3) * subscriber.receive("hello") // no máximo 3 vezes
_ * subscriber.receive("hello")      // qualquer número

Strict Mocking: não permite interações além das declaradas.

when:
publisher.send("hello")
 
then:
1 * subscriber.receive("hello")
_ * auditing._  // permite qualquer interação com auditing
0 * _           // não permite nenhuma outra interação

Stubbing

Fazer metodos sempre responderem de certa forma.

Retornar Valores Fixos

subscriber.receive(_) >> "ok"
subscriber.receive("message1") >> "ok"
subscriber.receive("message2") >> "fail"
subscriber.receive(_) >> { throw new InternalError("ouch") }
 
// Sequência de retornos: 1ra: "ok", 2ra: "error", 3ra e seguintes: "ok"
subscriber.receive(_) >>> ["ok", "error", "ok"]

Retornar Valores com Lógica

// Com lista de argumentos
subscriber.receive(_) >> { args -> args[0].size() > 3 ? "ok" : "fail" }
 
// Com argumentos mapeados (preferível)
subscriber.receive(_) >> { String message -> message.size() > 3 ? "ok" : "fail" }

Combinar Mocking e Stubbing

Devem acontecer na mesma interaction:

1 * subscriber.receive("message1") >> "ok"
1 * subscriber.receive("message2") >> "fail"

Stubs

Criados com Stub() , apenas para verificar retorno (não para interações).

Subscriber subscriber = Stub()

Diferenças do Mock:

  • Invocações não esperadas retornam valores “dummy” mais elaborados (objetos vazios, instâncias padrão).
  • Não pode ter interações mandatórias (1 * ...).
  • Comunica ao leitor que é apenas um colaborador passivo.
Subscriber subscriber = Stub {
    receive("message1") >> "ok"
    receive("message2") >> "fail"
}