Guia de extensões Spock

Guia de extensões Spock

1. Visão geral

Neste tutorial, daremos uma olhada nas extensõesSpock.

Às vezes, podemos precisar modificar ou aprimorar o ciclo de vida de nossas especificações. Por exemplo, gostaríamos de adicionar alguma execução condicional, repetir no teste de integração com falha aleatória e muito mais. Para isso, podemos usar o mecanismo de extensão de Spock.

Spock has a wide range of various extensions que podemos conectar ao ciclo de vida de uma especificação.

Vamos descobrir como usar as extensões mais comuns.

2. Dependências do Maven

Antes de começar, vamos configurar nossoMaven dependencies:


    org.spockframework
    spock-corez
    1.3-groovy-2.4
    test


    org.codehaus.groovy
    groovy-all
    2.4.7
    test

3. Extensões baseadas em anotações

A maioria deSpock‘s built-in extensions are based on annotations.

Podemos adicionar anotações em uma classe ou recurso de especificação para acionar um comportamento específico.

3.1. @Ignore

Sometimes we need to ignore some feature methods or spec classes. Assim, podemos precisar mesclar nossas alterações o mais rápido possível, mas a integração contínua ainda falha. Podemos ignorar algumas especificações e ainda assim fazer uma fusão bem-sucedida.

Podemos usar@Ignore em um nível de método para pular um único método de especificação:

@Ignore
def "I won't be executed"() {
    expect:
    true
}

Spock won’t execute this test method. E a maioria dos IDEs marcará o teste comoskipped.

Além disso, podemos usar@Ignore on o nível da classe:

@Ignore
class IgnoreTest extends Specification

Podemos simplesmenteprovide a reason why nosso conjunto de testes ou método for ignorado:

@Ignore("probably no longer needed")

3.2. @IgnoreRest

Da mesma forma, podemos ignorar todas as especificações, exceto uma, que podemos marcar com uma anotação@IgnoreRest:

def "I won't run"() { }

@IgnoreRest
def 'I will run'() { }

def "I won't run too"() { }

3.3. @IgnoreIf

Às vezes, gostaríamos de ignorar condicionalmente um ou dois testes. Nesse caso,we can use @IgnoreIf, which accepts a predicate como argumento:

@IgnoreIf({System.getProperty("os.name").contains("windows")})
def "I won't run on windows"() { }

O Spock fornece o conjunto de propriedades e classes auxiliares, para facilitar a leitura e gravação de nossos predicados:

  • os – Informações sobre o sistema operacional (consultespock.util.environment.OperatingSystem).

  • jvm - as informações da JVM (consultespock.util.environment.Jvm).

  • sys – Propriedades do sistema em um mapa.

  • env - Variáveis ​​de ambiente em um mapa.

Podemos reescrever o exemplo anterior usandoos property. Na verdade, é a classespock.util.environment.OperatingSystem com alguns métodos úteis, como por exemploisWindows():

@IgnoreIf({ os.isWindows() })
def "I'm using Spock helper classes to run only on windows"() {}

Observe queSpock usesSystem.getProperty(…) oltura. O objetivo principal é fornecer uma interface clara, em vez de definir regras e condições complicadas.

Além disso, como nos exemplos anteriores, podemos aplicar a anotação@IgnoreIf no nível da classe.

3.4. @Requires

Às vezes, é mais fácil inverter nossa lógica de predicado de@IgnoreIf. Nesse caso, podemos usar@Requires:

@Requires({ System.getProperty("os.name").contains("windows") })
def "I will run only on Windows"()

Portanto, enquanto o@Requires makes this test run only if the OS is Windows, o@IgnoreIf, ustentando o mesmo predicado, faz com que o teste seja executado apenas se o sistema operacional fornot Windows.

In general,it’s much better to say under which condition the test will execute, rather than when it gets ignored.

3.5. @PendingFeature

EmTDD, we escreva os testes primeiro. Então, precisamos escrever um código para fazer esses testes passarem. Em alguns casos, precisaremos confirmar nossos testes antes que o recurso seja implementado.

Este é um bom caso de uso para@PendingFeature:

@PendingFeature
def 'test for not implemented yet feature. Maybe in the future it will pass'()

Há uma diferença principal entre@Ignore e@PendingFeature. Em@PedingFeature, os stests são executados, mas todas as falhas são ignoradas.

Se um teste marcado com@PendingFeature for enviado sem erro, ele será relatado como uma falha, para lembrar sobre a remoção da anotação.

Dessa forma, podemos inicialmente ignorar falhas de recursos não implementados, mas, no futuro, essas especificações farão parte dos testes normais, em vez de serem ignoradas para sempre.

3.6. @Stepwise

Podemos executar os métodos de uma especificação em uma determinada ordem com a anotação@Stepwise:

def 'I will run as first'() { }

def 'I will run as second'() { }

In general, tests should be deterministic. Um não deve depender do outro. É por isso que devemos evitar o uso de@Stepwise annotation.

Mas se for necessário, precisamos estar cientes de que@Stepwise doesn’t override the behavior of @Ignore, @IgnoreRest, or@IgnoreIf. Devemos ter cuidado ao combinar essas anotações com@Stepwise.

3.7. @Timeout

Podemos limitar o tempo de execução de um único método de especificação e falhar antes:

@Timeout(1)
def 'I have one second to finish'() { }

Observe que esse é o tempo limite para uma única iteração, sem contar o tempo gasto nos métodos de fixação.

Por padrão, ospock.lang.Timeout usa os segundos como unidade de tempo base. Mas,we can specify other time units:

@Timeout(value = 200, unit = TimeUnit.SECONDS)
def 'I will fail after 200 millis'() { }

@Timeout no nível da classe tem o mesmo efeito que aplicá-lo a cada método de recurso separadamente:

@Timeout(5)
class ExampleTest extends Specification {

    @Timeout(1)
    def 'I have one second to finish'() {

    }

    def 'I will have 5 seconds timeout'() {}
}

Usar@Timeout em um único método de especificação sempre substitui o nível de classe.

3.8. @Retry

Às vezes, podemos ter alguns testes de integração não determinísticos. Eles podem falhar em algumas execuções por motivos como processamento assíncrono ou dependendo da resposta de outros clientesHTTP. Além disso, o servidor remoto com compilação e IC falhará e nos forçará a executar os testes e compilar novamente.

Para evitar essa situação,we can use @Retry annotation on a method or class level, to repeat failed tests:

@Retry
def 'I will retry three times'() { }

Por padrão, ele tentará novamente três vezes.

É muito útil determinar as condições sob as quais devemos repetir nosso teste. Podemos especificar a lista de exceções:

@Retry(exceptions = [RuntimeException])
def 'I will retry only on RuntimeException'() { }

Ou quando houver uma mensagem de exceção específica:

@Retry(condition = { failure.message.contains('error') })
def 'I will retry with a specific message'() { }

Muito útil é uma nova tentativa com um atraso:

@Retry(delay = 1000)
def 'I will retry after 1000 millis'() { }

E, finalmente, como quase sempre, podemos especificar uma nova tentativa no nível da classe:

@Retry
class RetryTest extends Specification

3.9. @RestoreSystemProperties

Podemos manipular variáveis ​​de ambiente com@RestoreSystemProperties.

Essa anotação, quando aplicada, salva o estado atual das variáveis ​​e as restaura posteriormente. Também inclui métodossetup oucleanup:

@RestoreSystemProperties
def 'all environment variables will be saved before execution and restored after tests'() {
    given:
    System.setProperty('os.name', 'Mac OS')
}

Observe quewe shouldn’t run the tests concurrently when we’re manipulating the system properties. Nossos testes podem ser não determinísticos.

3.10. Títulos amigáveis ​​ao ser humano

Podemos adicionar um título de teste amigável usando a anotação@Title:

@Title("This title is easy to read for humans")
class CustomTitleTest extends Specification

Da mesma forma, podemos adicionar uma descrição da especificação com a anotação@Narrative e com uma stringGroovy Sde várias linhas:

@Narrative("""
    as a user
    i want to save favourite items
    and then get the list of them
""")
class NarrativeDescriptionTest extends Specification

3.11. @See

Para vincular uma ou mais referências externas, podemos usar a anotação @See:

@See("https://example.org")
def 'Look at the reference'()

Para passar mais de um link, podemos usar o operando Groovy[] para criar uma lista:

@See(["https://example.org/first", "https://example.org/first"])
def 'Look at the references'()

3.12. @Issue

Podemos denotar que um método de recurso se refere a um problema ou vários problemas:

@Issue("https://jira.org/issues/LO-531")
def 'single issue'() {

}

@Issue(["https://jira.org/issues/LO-531", "http://jira.org/issues/LO-123"])
def 'multiple issues'()

3.13. @Subject

E, finalmente, podemos indicar qual classe é a classe em teste com@Subject:

@Subject
ItemService itemService // initialization here...

No momento, é apenas para fins informativos.

4. Configurando extensões

We can configure some of the extensions in the Spock configuration file. Isso inclui a descrição de como cada extensão deve se comportar.

Normalmente, criamos um arquivo de configuração em Groovyescalado, por exemplo,SpockConfig.groovy.

Claro,Spock needs to find our config file. Primeiro de tudo, ele lê um local personalizado da propriedade do sistemaspock.configuration e, em seguida, tenta encontrar o arquivo no caminho de classe. Quando não encontrado, ele vai para um local no sistema de arquivos. Se ainda não for encontrado, ele procuraSpockConfig.groovy em o classpath de execução do teste.

Eventualmente, Spock vai para a casa de um usuário Spock, que é apenas um diretório.spock dentro do nosso diretório inicial. Podemos mudar este diretório configurando a propriedade do sistema chamadaspock.user.home or por uma variável de ambienteSPOCK_USER_HOME.

Para nossos exemplos, vamos criar um arquivoSpockConfig.groovy e colocá-lo no classpath (src/test/resources/SpockConfig.Groovy).

4.1. Filtrando o rastreamento de pilha

Usando um arquivo de configuração, podemos filtrar (ou não) os rastreamentos de pilha:

runner {
    filterStackTrace false
}

O valor padrão étrue.

Para ver como funciona e praticar, vamos criar um teste simples que lança umRuntimeException:

def 'stacktrace'() {
    expect:
    throw new RuntimeException("blabla")
}

QuandofilterStackTrace  é definido como falso, veremos na saída:

java.lang.RuntimeException: blabla

  at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
  at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105)
  at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:60)
  at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:235)
  at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:247)
  // 34 more lines in the stack trace...

Definindo essa propriedade paratrue,, obteremos:

java.lang.RuntimeException: blabla

  at extensions.StackTraceTest.stacktrace(StackTraceTest.groovy:10)

Embora tenha em mente, às vezes é útil ver o rastreamento de pilha completo.

4.2. Recursos condicionais no arquivo de configuração Spock

Às vezes,we might need to filter stack traces conditionally. Por exemplo, precisaremos ver rastreamentos de pilha completos em uma ferramenta de integração contínua, mas isso não é necessário em nossa máquina local.

Podemos adicionar uma condição simples, baseada, por exemplo, nas variáveis ​​de ambiente:

if (System.getenv("FILTER_STACKTRACE") == null) {
    filterStackTrace false
}

O arquivo de configuração do Spock é um arquivo Groovy, portanto, pode conter trechos de código Groovy.

4.3. Prefixo e URL em@Issue

Anteriormente, falamos sobre a anotação@Issue. Também podemos configurar isso usando o arquivo de configuraçãoby defining a common URL part with issueUrlPrefix. 

A outra propriedade éissueNamePrefix. Então, todo valor de@Issue é precedido pela propriedadeissueNamePrefix.

Precisamos adicionar essas duas propriedades emthe report:

report {
    issueNamePrefix 'Bug '
    issueUrlPrefix 'https://jira.org/issues/'
}

4.4. Otimizar ordem de execução

The other very helpful tool is optimizeRunOrder. Spock pode se lembrar de quais especificações falharam e com que frequência e quanto tempo é necessário para executar um método de recurso.

Com base nesse conhecimento,Spock executará primeiro os recursos que falharam na última execução. Em primeiro lugar, ele executará as especificações que falharam mais sucessivamente. Além disso, as especificações mais rápidas serão executadas primeiro.

Este comportamento pode ser habilitado no arquivo de configuração . Para habilitar o otimizador, usamosoptimizeRunOrder property:

runner {
  optimizeRunOrder true
}

Por padrão, o otimizador para ordem de execução está desabilitado.

4.5. Incluindo e excluindo especificações

Spock pode excluir ou incluir determinadas especificações. We can lean on classes, super-classes, interfaces or annotations, which are applied on specification classes. A biblioteca pode ser capaz de excluir ou incluir recursos únicos, com base na anotação em um nível de recurso.

Podemos simplesmente excluir um conjunto de testes da classeTimeoutTest usando a propriedadeexclude:

import extensions.TimeoutTest

runner {
    exclude TimeoutTest
}

TimeoutTest and all its subclasses will be excluded. SeTimeoutTest fosse uma anotação aplicada em uma classe de especificação, então esta especificação seria excluída.

Podemos especificar anotações e classes base separadamente:

import extensions.TimeoutTest
import spock.lang.Issue
    exclude {
        baseClass TimeoutTest
        annotation Issue
}

O exemplo acimawill exclude test classes or methods with the @Issue annotation as well as TimeoutTest or any of its subclasses.

Para incluir qualquer especificação, simplesmente usamosinclude property. Podemos definir as regras deinclude da mesma forma queexclude.

4.6. Criando um relatório

Based on the test results and previously known annotations, we can generate a report with Spock. Além disso, este relatório conterá coisas como valores@Title, @See, @Issue, and @Narrative.

Podemos ativar a geração de um relatório no arquivo de configuração. Por padrão, ele não gerará o relatório.

Tudo o que precisamos fazer é passar valores para algumas propriedades:

report {
    enabled true
    logFileDir '.'
    logFileName 'report.json'
    logFileSuffix new Date().format('yyyy-MM-dd')
}

As propriedades acima são:

  • enabled  - deve ou não gerar o relatório

  • logFileDir – diretório do relatório

  • logFileName – the nome do relatório

  • logFileSuffix - um sufixo para cada nome de base de relatório gerado separado por um traço

Quando definimosenabled paratrue,, então é obrigatório definirlogFileDirelogFileName properties. OlogFileSuffix é opcional.

Também podemos definir todos eles nas propriedades do sistema:enabled,spock.logFileDir,spock.logFileNameespock.logFileSuffix.

5. Conclusão

Neste artigo, descrevemos as extensões Spock mais comuns.

We know that most of them are based on annotations. Além disso, aprendemos como criar um arquivo de configuraçãoSpock  e quais são as opções de configuração disponíveis. Em resumo, nosso conhecimento recém-adquirido é muito útil para escrever testes eficazes e fáceis de ler.

A implementação de todos os nossos exemplos pode ser encontrada em nossoGithub project.