Rédaction de spécifications avec Kotlin et Spek

Rédaction de spécifications avec Kotlin et Spek

1. introduction

Specification Testing frameworks are complementary to Unit Testing frameworks for testing our applications.

Dans ce didacticiel, nous allons présenter leSpek framework - un framework de test de spécification pour Java et Kotlin.

2. Qu'est-ce que le test de spécification?

En termes simples, dans les tests de spécification, nous commençons par la spécification et décrivons l'intention du logiciel, au lieu de sa mécanique.

Cela est souvent mis à profit dans Behavior Driven Development car le but est de valider un système par rapport aux spécifications prédéfinies de notre application.

Les cadres de test de spécification couramment connus incluentSpock,Cucumber,Jasmine etRSpec.

2.1. Qu'est-ce que Spek?

Spek is a Kotlin-based Specification Testing framework for the JVM. Il est conçu pour fonctionner en tant queJUnit 5 Test Engine. Cela signifie que nous pouvons facilement le brancher sur tout projet utilisant déjà JUnit 5 pour être exécuté parallèlement à tout autre test que nous pourrions avoir.

Il est également possible d’exécuter les tests à l’aide de l’ancienne infrastructure JUnit 4, en utilisant la dépendance JUnit Platform Runner si nécessaire.

2.2. Dépendances Maven

Pour utiliser Spek, nous devons ajouter les dépendances requises à notre build Maven:


    org.jetbrains.spek
    spek-api
    1.1.5
    test


    org.jetbrains.spek
    spek-junit-platform-engine
    1.1.5
    test

La dépendancespek-api est l'API réelle utilisée pour le framework de test. Il définit tout ce avec quoi nos tests fonctionneront. La dépendancespek-junit-platform-engine est alors le moteur de test JUnit 5 nécessaire pour exécuter nos tests.

Notez que toutes les dépendances de Spek doivent avoir la même version. La dernière version peut être trouvée surhere.

2.3. Premier test

Une fois que Spek est configuré, l'écriture de tests est un cas simple d'écriture de la classe correcte dans la structure correcte. Ceci est légèrement inhabituel pour le rendre plus lisible.

Spek exige que nos tests héritent tous d'une superclasse appropriée - généralementSpek - et que nous implémentions nos tests en passant un bloc au constructeur de cette classe:

class FirstSpec : Spek({
    // Implement the test here
})

3. Styles de test

Specification Testing emphasizes writing tests in a way that’s as readable as possible. Cucumber, par exemple, écrit le test dans son intégralité dans un langage lisible par l'homme, puis le lie aux étapes pour que le code reste séparé.

Spek works by using special methods that act as readable strings, dont chacun reçoit un bloc à exécuter le cas échéant. Il existe certaines variations sur les fonctions que nous utilisons en fonction de la manière dont nous voulons que les tests soient lus.

3.1. given/on/it

Une façon de rédiger nos tests consiste à utiliser le style "donné / donné / donné".

Cela utilise des méthodes appeléesgiven,on etit, imbriquées dans cette structure, pour écrire nos tests:

  • given - définit les conditions initiales du test

  • on - exécute l'action de test

  • it - affirme que l'action de test s'est déroulée correctement

Nous pouvons avoir autant de blocs que nécessaire mais nous devons les imbriquer dans cet ordre:

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)
            }
        }
    }
})

Ce test se lit très facilement. En se concentrant sur les étapes du test, on peut le lire comme suit: «Étant donné une calculatrice, lors de l’ajout de 3 et 5, il en produit 8».

3.2. describe/it

L'autre façon dont nous pouvons écrire nos tests est dans le style "décrire / le". Au lieu de cela, cela utilise la méthodedescribe pour l'ensemble de l'imbrication, et continue d'utiliserit pour nos assertions.

Dans ce cas, nous pouvons imbriquer les méthodesdescribe autant que nous en avons besoin pour écrire nos tests:

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)
            }
        }
    }
})

La structure des tests utilisant ce style est moins stricte, ce qui signifie que nous avons beaucoup plus de flexibilité dans la façon dont nous écrivons les tests.

Malheureusement, l’inconvénient est que les tests ne se lisent pas aussi naturellement que lorsque nous utilisons «given / on / it».

3.3. Styles supplémentaires

Spek doesn’t enforce these styles, and it will allow for the keywords to be interchanged as much as desired. Les seules exigences sont que toutes les assertions existent à l'intérieur d'unit et qu'aucun autre bloc ne soit trouvé à ce niveau.

La liste complète des mots clés d'imbrication disponibles est la suivante:

  • donné

  • on

  • décris

  • le contexte

Nous pouvons les utiliser pour donner à nos tests la meilleure structure possible pour la façon dont nous voulons les écrire.

3.4. Tests basés sur les données

Les mécanismes utilisés pour définir les tests ne sont rien de plus que de simples appels de fonctions. Cela signifie que nous pouvons faire d'autres choses avec eux, comme n'importe quel code normal. In particular, we can call them in a data-driven way if we so desire.

Pour ce faire, le moyen le plus simple consiste à effectuer une boucle sur les données à utiliser et à appeler le bloc approprié depuis cette boucle:

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())
                }
            }
        }
    }
})

Nous pouvons faire toutes sortes de choses comme celle-ci si nous en avons besoin, mais c'est probablement le plus utile.

4. Assertions

Spek ne prescrit aucune manière particulière d'utiliser les assertions. Au lieu de cela,it allows us to use whatever assertion framework we’re most comfortable with.

Le choix évident sera la classeorg.junit.jupiter.api.Assertions, car nous utilisons déjà le framework JUnit 5 comme testeur.

Cependant, nous pouvons également utiliser toute autre bibliothèque d'assertions que nous voulons si elle améliore nos tests - par exemple,Kluent,Expekt ouHamKrest.

L'avantage d'utiliser ces bibliothèques au lieu de la classe standard JUnit 5Assertions est dû à la lisibilité des tests.

Par exemple, le test ci-dessus réécrit à l'aide de Kluent se lit comme suit:

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

Comme avec la plupart des frameworks de test,Spek can also execute logic before/after tests.

Ce sont, comme leur nom l'indique, des blocs exécutés avant ou après le test lui-même.

Les options ici sont:

  • beforeGroup

  • afterGroup

  • beforeEachTest

  • afterEachTest

Ceux-ci peuvent être placés dans l'un des mots-clés imbriqués et s'appliqueront à tout ce qui se trouve dans ce groupe.

La façon dont Spek fonctionne, tout le code à l'intérieur de l'un des mots-clés d'imbrication est exécuté immédiatement au début du test, mais les blocs de contrôle sont exécutés dans un ordre particulier centré autour des blocsit.

En travaillant de l'extérieur vers l'intérieur, Spek exécutera chaque blocbeforeEachTest immédiatement avant chaque blocit imbriqué dans le même groupe, et chaque blocafterEachTest immédiatement après chaque blocit. De même, Spek exécutera chaque bloc sbeforeGroup juste avant chaque groupe et chaque blocafterGroup immédiatement après chaque groupe dans l'imbrication actuelle.

Ceci est compliqué et s’explique mieux avec un exemple:

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")
            }
        }
    }
})

Le résultat de l'exécution de ce qui précède est:

BeforeGroup 0
BeforeGroup 1
BeforeEachTest 0
BeforeEachTest 1
Test 1
AfterEachTest 1
AfterEachTest 0
AfterGroup 1
AfterGroup 0

Tout de suite, nous pouvons voir que les blocs externesbeforeGroup/afterGroup sont autour de l'ensemble des tests, tandis que les blocs internesbeforeGroup/afterGroup sont uniquement autour des tests dans le même contexte.

Nous pouvons également voir que tous les blocsbeforeGroup sont exécutés avant tout bloc sbeforeEachTest et l'inverse pourafterGroup/afterEachTest.

Un exemple plus large de ceci, montrant l'interaction entre plusieurs tests dans plusieurs groupes, peut être vuon GitHub.

6. Sujets de test

Many times, we will be writing a single Spec for a single Test Subject. Spek offre un moyen pratique d’écrire cela, en gérant le sujet sous test pour nous automatiquement. 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. Ceci définit le sujet du test. Nous pouvons alors nous référer à cela à partir de n'importe lequel de nos codes de test en tant quesubject.

Nous pouvons utiliser ceci pour réécrire notre test précédent de la calculatrice comme suit:

class CalculatorTest : SubjectSpek({
    subject { Calculator() }
    describe("A calculator") {
        describe("Addition") {
            val result = subject.add(3, 5)
            it("Produces the correct answer") {
                assertEquals(8, result)
            }
        }
    }
})

Cela peut sembler peu, mais cela peut aider à rendre les tests beaucoup plus lisibles, surtout quand il y a un grand nombre de tests à prendre en compte.

6.1. Dépendances Maven

Pour utiliser l'extension de sujet, nous devons ajouter une dépendance à notre construction Maven:


    org.jetbrains.spek
    spek-subject-extension
    1.1.5
    test

7. Sommaire

Spek est un cadre puissant permettant des tests très lisibles, ce qui signifie que toutes les parties de l’organisation peuvent les lire.

Ceci est important pour permettre à tous les collègues de contribuer au test de l’ensemble de l’application.

Enfin, des extraits de code, comme toujours, peuvent être trouvésover on GitHub.