Blog

A la découverte de Spock, le framework de tests en Groovy

Introduction

A l’occasion de la sortie de la version 2.0 du framework Spock, j’ai décidé de regarder comment il fonctionne, ses intérêts et points forts.

Spock est un framework de tests et de spécifications pour Java et Groovy, permettant un développement de type BDD. La première version date de 2009 (12 ans !) et la version 2.0 vient d’être publié le 17 mai 2021.

Spock 2.0 utilise la plateforme JUnit 5 et demande au moins  Java 8 et Groovy-2.5+ (Groovy 3.0 est recommandé spécialement pour les projets utilisant Java 12 ou plus).

Afin de tester Spock, j’ai décidé de pratiquer le kata « Fizz Buzz » en utilisant le framework Spock au lieu de JUnit comme généralement.

Après avoir configurer mon IDE et créer un nouveau projet Maven, je peux commencer.

Première spécification

Première spécificité de Spock, on ne crée pas un fichier de tests mais un fichier de spécifications, écrit en Groovy. La démarche est clairement une démarche BDD  dès le départ.

Pour définir qu’il s’agit d’une classe de spécification, la classe doit étendre la classe Specification dans le package spok.lang du framework.

class FizzBuzzSpec extends Specification {

    def "should return 1 when input is 1"() {
        given:

        when:

        then:
    }

On créé ensuite sa première méthode de test. On constate 2 éléments :

  • le nom de la méthode est une chaîne libre entre guillemets, permettant une définition claire de son action
  • une méthode est ensuite séparée en 3 blocs : given, when, then. Le BDD encore une fois est mis en avant par le framework

L’écriture du contenu des blocs est très simple

def "should return 1 when input is 1"() {
    given:
    def input = 1
    def expected = "1"
    def fizzBuzz = new FizzBuzz()

    when:
    def actual = fizzBuzz.play(input)

    then:
    expected.equals(actual)
}

Le bloc « then » est une expession qui renvoit un booléen.

Le moteur d’exécution des tests étant JUnit 5, IntelliJ ou Maven peuvent exécuter le test Spock de la même manière qu’un test JUnit standard. L’exécution du test renvoie une erreur, le code n’étant pas encore implémenté :

Condition not satisfied:

expected.equals(actual)
|        |      |
1        false  null

Le retour dans la console permet de comprendre rapidement la cause de l’erreur.

Après implémentation, le résultat passe au vert et je peux passer au test, pardon à la spécification suivante.

Data Driven Testing

Après avoir spécifié et implémenté les cas, 1, 2, 3 et 5, je décide d’utiliser une fonctionnalité de Spock, le Data Driven Testing afin d’écrire une seule méthode de spécification pour plusieurs chiffres non multiples de 3 et de 5. L’idée du Data Driven Testing rappelle les tests paramétrés de JUnit mais permet de définir les différents paramètres dans un bloc « where » de la même façon que dans un fichier Gherkin pour Cucumber.

def "should return input when input is not a multiple  of 3 or 5"() {
    given:
    def actual = fizzBuzz.play(input)

    expect:
    expected.equals(actual)

    where:
    input | expected
    1     | "1"
    2     | "2"
    4     | "4"
    7     | "7"
    8     | "8"
    11    | "11"
}

La structure de la spécification est un peut différente avec la présence d’un bloc « expect » au lieu du bloc « then » suivi d’un bloc  « where » contenant un  tableau de données. Les titres des colonnes du tableau sont les noms des variables utilisées dans le code, mettant en relation le input et le expected. L’intérêt dans ce cas est de générer 6 tests unitaires en une seule méthode.

Une contrainte des données tabulaires est qu’elles doivent avoir au minimum 2 colonnes. Il est cependant possible de créer une colonne vide en la remplissant avec des « _ »

def "should return FizzBuzz when input is multiple of 3 and 5"() {
    given:
    def actual = fizzBuzz.play(input)
    def expected = "FizzBuzz"

    expect:
    expected.equals(actual)

    where:
    input | _
    15    | _
    30    | _
    45    | _
}

Le bloc « where » ne prend pas uniquement des données tabulaires mais peut définir des valeurs comme des résultats d’appel de méthodes ou des requêtes SQL (voir les exemples d’utilisation des Data Pipe dans la documentation https://spockframework.org/spock/docs/2.0/data_driven_testing.html#_data_pipes)

 Code Coverage, Sonar et autres fonctionnalités

Comme Spock utilise la plateforme JUnit pour exécuter les tests, nous disposons de tous les outils disponibles pour JUnit, typiquement, il est possible d’utiliser les outils de couverture de code et de reporting des tests, que ce soit dans l’IDE ou dans Sonar.

Spock intègre directement dans le framework une partie Mocking assez puissante. Il permet aussi de s’intégrer avec Spring et le contexte de tests de Spring.

Il est encore possible de donner un label à ses blocs Given / When / Then permettant encore d’accentuer l’aspect BDD.

Conclusion

Spock s’avère un framework de tests à la fois puissant et simple d’accès dont je n’ai fait que survoler les fonctionnalités de base. La documentation officielle est claire et accessible, permettant de découvrir simplement le framework.

L’aspect BDD extrêmement mis en avant dans l’écriture des tests est un énorme point positif.

Tout développeur d’un projet Groovy doit clairement regarder Spock dans ces outils de bases. Et pour des projets Java standard, il est aussi intéressant, justifiant d’utiliser (et d’apprendre) Groovy. Notons cependant qu’il est possible de garder une syntaxe proche du Java standard dans son écriture des spécifications si on est rebuter par certains éléments de Groovy.

 

Live long and prosper

Written by

The author didnt add any Information to his profile yet

  • Christophe [Ox]

    Effectivement, cela semble vraiment intéressant !