Escrevendo especificações com Kotlin e Spek
1. Introdução
Specification Testing frameworks are complementary to Unit Testing frameworks for testing our applications.
Neste tutorial, apresentaremos oSpek framework - uma estrutura de teste de especificação para Java e Kotlin.
2. O que é o teste de especificação?
Simplificando, no Teste de Especificação, começamos com a especificação e descrevemos a intenção do software, ao invés de sua mecânica.
Isso geralmente é aproveitado no Behavior Driven Development, uma vez que a intenção é validar um sistema de acordo com especificações predefinidas de nosso aplicativo.
2.1. O que é Spek?
Spek is a Kotlin-based Specification Testing framework for the JVM. Ele foi projetado para funcionar como umJUnit 5 Test Engine. Isso significa que podemos conectá-lo facilmente a qualquer projeto que já use o JUnit 5 para executar juntamente com outros testes que possamos ter.
Também é possível executar os testes usando a estrutura JUnit 4 mais antiga, usando a dependência JUnit Platform Runner, se necessário.
2.2. Dependências do Maven
Para usar o Spek, precisamos adicionar as dependências necessárias à nossa construção Maven:
org.jetbrains.spek
spek-api
1.1.5
test
org.jetbrains.spek
spek-junit-platform-engine
1.1.5
test
A dependênciaspek-api é a API real usada para a estrutura de teste. Ele define tudo com o qual nossos testes trabalharão. A dependênciaspek-junit-platform-engine é então o mecanismo de teste JUnit 5 necessário para executar nossos testes.
Observe que todas as dependências Spek precisam ter a mesma versão uma da outra. A versão mais recente pode ser encontrada emhere.
2.3. Primeiro teste
Depois que o Spek é configurado, escrever testes é um caso simples de escrever a classe correta na estrutura correta. Isso é um pouco incomum para torná-lo mais legível.
Spek requer que todos os nossos testes sejam herdados de uma superclasse apropriada - normalmenteSpek - e que implementemos nossos testes passando um bloco para o construtor desta classe:
class FirstSpec : Spek({
// Implement the test here
})
3. Estilos de teste
Specification Testing emphasizes writing tests in a way that’s as readable as possible. O pepino, por exemplo, grava o teste inteiro em linguagem legível por humanos e, em seguida, vincula-o a etapas para que o código seja mantido separado.
Spek works by using special methods that act as readable strings, cada um dos quais recebe um bloco para executar conforme apropriado. Existem algumas variações de quais funções usamos, dependendo da maneira como queremos que os testes sejam lidos.
3.1. given/on/it
Uma maneira de escrevermos nossos testes é no estilo "dado / sobre ele".
Isso usa métodos chamadosgiven,oneit, aninhados nessa estrutura, para escrever nossos testes:
-
given - define as condições iniciais para o teste
-
on - executa a ação de teste
-
it - afirma que a ação de teste foi executada corretamente
Podemos ter o máximo de cada bloco que precisarmos, mas devemos aninhá-los nesta ordem:
class CalculatorTest : Spek({
given("A calculator") {
val calculator = Calculator()
on("Adding 3 and 5") {
val result = calculator.add(3, 5)
it("Produces 8") {
assertEquals(8, result)
}
}
}
})
Este teste lê com muita facilidade. Focando as etapas do teste, podemos lê-lo como "Dada uma calculadora, Ao adicionar 3 e 5, produz 8".
3.2. describe/it
A outra maneira pela qual podemos escrever nossos testes é no estilo "descrever / it". Em vez disso, ele usa o métododescribe para todos os aninhamentos e continua usandoit para nossas asserções.
Nesse caso, podemos aninhar os métodosdescribe tanto quanto precisamos para escrever nossos testes:
class CalculatorTest : Spek({
describe("A calculator") {
val calculator = Calculator()
describe("Addition") {
val result = calculator.add(3, 5)
it("Produces the correct answer") {
assertEquals(8, result)
}
}
}
})
Há menos estrutura aplicada nos testes usando esse estilo, o que significa que temos muito mais flexibilidade na forma como escrevemos os testes.
Infelizmente, a desvantagem disso é que os testes não funcionam tão naturalmente quanto quando usamos "dado / ligado / isso".
3.3. Estilos adicionais
Spek doesn’t enforce these styles, and it will allow for the keywords to be interchanged as much as desired. Os únicos requisitos são que todas as asserções existam dentro de umite nenhum outro bloco seja encontrado nesse nível.
A lista completa de palavras-chave de aninhamento disponíveis é:
-
dado
-
on
-
descrever
-
contexto
Podemos usá-los para fornecer a nossos testes a melhor estrutura possível para a forma como queremos escrevê-los.
3.4. Testes Orientados a Dados
O mecanismo usado para definir testes nada mais é do que simples chamadas de função. Isso significa que podemos fazer outras coisas com eles, como qualquer código normal. In particular, we can call them in a data-driven way if we so desire.
A maneira mais fácil de fazer isso é fazer um loop sobre os dados que queremos usar e chamar o bloco apropriado de dentro desse loop:
class DataDrivenTest : Spek({ describe("A data driven test") { mapOf( "hello" to "HELLO", "world" to "WORLD" ).forEach { input, expected -> describe("Capitalising $input") { it("Correctly returns $expected") { assertEquals(expected, input.toUpperCase()) } } } } })
Podemos fazer todo tipo de coisa assim se precisarmos, mas isso é provavelmente o mais útil.
4. Asserções
Spek não prescreve nenhuma maneira particular de usar asserções. Em vez disso,it allows us to use whatever assertion framework we’re most comfortable with.
A escolha óbvia será a classeorg.junit.jupiter.api.Assertions, uma vez que já estamos usando a estrutura JUnit 5 como nosso executor de teste.
No entanto, também podemos usar qualquer outra biblioteca de asserção que quisermos, se isso tornar nossos testes melhores - por exemplo,Kluent,Expekt ouHamKrest.
O benefício de usar essas bibliotecas em vez da classe JUnit 5Assertions padrão é devido à legibilidade dos testes.
Por exemplo, o teste acima reescrito usando o Kluent é lido como:
class CalculatorTest : Spek({
describe("A calculator") {
val calculator = Calculator()
describe("Addition") {
val result = calculator.add(3, 5)
it("Produces the correct answer") {
result shouldEqual 8
}
}
}
})
5. Before/After Handlers
Tal como acontece com a maioria dos frameworks de teste,Spek can also execute logic before/after tests.
Estes são, exatamente como o nome indica, blocos executados antes ou depois do próprio teste.
As opções aqui são:
-
beforeGroup
-
afterGroup
-
beforeEachTest
-
afterEachTest
Elas podem ser colocadas em qualquer uma das palavras-chave de aninhamento e serão aplicadas a tudo dentro desse grupo.
Da maneira como Spek funciona, todo o código dentro de qualquer uma das palavras-chave de aninhamento é executado imediatamente no início do teste, mas os blocos de controle são executados em uma ordem particular centrada em torno dos blocosit.
Trabalhando de fora para dentro, Spek executará cada blocobeforeEachTest imediatamente antes de cada blocoit aninhado no mesmo grupo, e cada blocoafterEachTest imediatamente após cada blocoit. Da mesma forma, Spek executará cada blocobeforeGroup imediatamente antes de cada grupo e cada blocoafterGroup imediatamente após cada grupo no aninhamento atual.
Isso é complicado e é melhor explicado com um exemplo:
class GroupTest5 : Spek({ describe("Outer group") { beforeEachTest { System.out.println("BeforeEachTest 0") } beforeGroup { System.out.println("BeforeGroup 0") } afterEachTest { System.out.println("AfterEachTest 0") } afterGroup { System.out.println("AfterGroup 0") } describe("Inner group 1") { beforeEachTest { System.out.println("BeforeEachTest 1") } beforeGroup { System.out.println("BeforeGroup 1") } afterEachTest { System.out.println("AfterEachTest 1") } afterGroup { System.out.println("AfterGroup 1") } it("Test 1") { System.out.println("Test 1") } } } })
O resultado da execução do acima é:
BeforeGroup 0
BeforeGroup 1
BeforeEachTest 0
BeforeEachTest 1
Test 1
AfterEachTest 1
AfterEachTest 0
AfterGroup 1
AfterGroup 0
De imediato, podemos ver que os blocosbeforeGroup/afterGroup externos estão em torno de todo o conjunto de testes, enquanto os blocosbeforeGroup/afterGroup internos estão apenas em torno dos testes no mesmo contexto.
Também podemos ver que todos os blocosbeforeGroup são executados antes de quaisquerbeforeEachTest blocks e o oposto paraafterGroup/afterEachTest.
Um exemplo maior disso, mostrando a interação entre vários testes em vários grupos, pode ser vistoon GitHub.
6. Assuntos de Teste
Many times, we will be writing a single Spec for a single Test Subject. A Spek oferece uma maneira conveniente de escrever isso, de modo que ele administre automaticamente o Assunto em teste para nós. We use the SubjectSpek base class instead of the Spek class for this.
When we use this, we need to declare a call to the subject block at the outermost level. Isso define o assunto do teste. Podemos então nos referir a isso em qualquer um de nossos códigos de teste comosubject.
Podemos usar isso para reescrever nosso teste anterior da calculadora da seguinte maneira:
class CalculatorTest : SubjectSpek({
subject { Calculator() }
describe("A calculator") {
describe("Addition") {
val result = subject.add(3, 5)
it("Produces the correct answer") {
assertEquals(8, result)
}
}
}
})
Pode não parecer muito, mas isso pode ajudar a tornar os testes muito mais legíveis, especialmente quando há um grande número de casos de teste a serem considerados.
6.1. Dependências do Maven
Para usar a Extensão do Assunto, precisamos adicionar uma dependência ao nosso build do Maven:
org.jetbrains.spek
spek-subject-extension
1.1.5
test
7. Sumário
Spek é uma estrutura poderosa que permite alguns testes muito legíveis, o que significa que todas as partes da organização podem lê-los.
Isso é importante para permitir que todos os colegas contribuam para testar o aplicativo inteiro.
Finalmente, trechos de código, como sempre, podem ser encontradosover on GitHub.