Spock : tester autrement
Quand on développe en Java, l’outil de référence pour écrire nos tests, c’est JUnit. Il y a quelques mois, nous avions d’ailleurs passé en revue l’état de l’art de l’écriture des tests avec cet outil, ici et là . Cependant, il existe des alternatives à JUnit, notamment TestNG, son principal challenger. Aujourd’hui, je voudrais vous présenter Spock, un framework de tests écrit en Groovy. Spock permet de tester du code Java et/ou Groovy. Même si il existe depuis plusieurs années, Spock n’a pas à mon avis la reconnaissance qu’il mérite.
Pourquoi adopter Spock ?
Les noms des tests Spock sont humainement compréhensibles
On peut utiliser une phrase comme nom pour un test Spock. Ceci améliore largement la lisibilité et donc la maintenance des tests. Personnellement, ayant adopté les noms de test rallongés avec des “_” comme séparateurs depuis quelques années, j’ai été heureux de pouvoir écrire des phrases humainement compréhensibles.
Spock intègre nativement la philosophie Behavior Driven Development
Dès lors qu’on peut utiliser une phrase comme nom de méthode, rien ne nous empêche de prendre une phrase directement des spécifications fonctionnelles. Par ailleurs, la structure native des tests avec Spock permet de distinguer facilement les différents blocs du test : given/when/then. Cela fait de Spock un excellent outil de Behavior Driven Development (BDD). D’ailleurs, ce n’est pas pour rien, que les tests Spock sont appelés Specification. Avec Spock, un outil tel que Cucumber devient optionnel pour faire du BDD.
Spock est un framework de test complet
Spock présente le grand intérêt d’être “tout en un” avec :
- gestion des assertions
- gestion des mocks
- fonctionnalités BDD
En effet, alors que JUnit délègue la gestion des mocks à des frameworks tiers, Spock nous offre nativement tout l’outillage nécessaire pour instrumentaliser nos composants. De même, la puissance de Groovy va nous permettre de nous passer d’une librairie d’assertion lorsque nous utilisons Spock alors que ces libraires sont devenues “indispensables” avec JUnit.
Outre le fait que ça fait des dépendances en moins à gérer, le fait d’avoir un outil unique nous évite surtout de devoir apprendre de nouvelles API (JSAssert, EasyMock, Cucumber etc.).
Spock est un bon voisin
Spock dispose d’un runner JUnit et Sputnik. Ceci permet d’intégrer nos tests Spock dans un projet basé sur JUnit sans avoir à les modifier.
Notions de Groovy
Les tests Spock sont écrits en Groovy. Même si la syntaxe Java est supportée à 100%, si on veut exploiter toute la puissance du framework, il est utile de connaître le langage Groovy. Je vous propose ci-dessous quelques concepts sur Groovy qui faciliteront la lecture de l’article ainsi que les premiers pas avec Spock.
Groovy se compile en byte code
Groovy est LE langage dynamique de la JVM et est donc parfaitement compatible avec Java. On peut ainsi appeler du code Java depuis Groovy et vice versa. C’est d’ailleurs ce qui nous permet de tester notre code Java avec des tests écrits en Groovy.
Le point virgule est optionnel, donc inutile
En Groovy, contrairement à Java, le point-virgule n’est pas nécessaire. Il vaut donc mieux ne pas le mettre.
Les déclarations de variables et de méthodes
Groovy est compatible Java, ce qui nous permet de déclarer les variables et les méthodes comme dans ce dernier, et donc d’avoir un typage statique.
Cependant, il existe une seconde façon de déclarer les méthodes et les variables en utilisant le mot clef “def”. Dans ce cas, le type de la variable, ou de retour de la fonction, est évalué dynamiquement à l’exécution.
def myVar = 15
Dans le cas ci-dessus, le type sera déduit de la valeur à l’exécution.
La méthode equals en Groovy
En Java “==” compare les références et la méthode “equals” nous permet de définir une condition d’égalité plus “métier”. En Groovy, “==” appelle directement la méthode “equals”. Ce qui fait qu’en général, on écrira simplement “==” au lieu de “equals”.
Condition de nullité en Groovy
En Java, nous sommes habitués à ce type de code :
Object myVar = doSomething();
if(null!=myVar){
//Effectuer un traitement
}
Avec Groovy, nous pourrions écrire :
Object myVar = doSomething()
if(myVar){
//Effectuer un traitement
}
Les tableaux en Groovy
En pratique, en Groovy on utilisera plutôt des java.util.List. Par exemple, l’expression ci-dessous permet de définir une java.util.List de String
def myList = ["POLE AGILE", "POLE JAVA", "POLE .NET"]
Les collections
Etant donnée une java.util.Collection myCollection, on dispose des méthodes suivantes sur l’objet myCollection :
- collect : transformer une collection de quelque chose en une collection d’autre chose
- find renvoie le premier élément de la collection qui vérifie une condition reçue en paramètre
- findAll renvoie tous les éléments de la collection qui vérifient une condition reçue en paramètre
Ces trois méthodes prennent en paramètre une méthode, une closure, qui définit le traitement à effectuer.
A l’intérieur de ces closures, il existe une variable prédéfinie : it qui représente l’élément courant de la liste. Par exemple, examinons le bout de code suivant :
def myCollection = ["red", "blue"]
def otherCollection = myCollection.findAll { it == "blue"}
L’expression it == “blue” nous permet d’indiquer quelle condition doit être appliquée à chaque item de la collection initiale. it est une variable “implicite”. Nous n’avons pas à la créer mais elle existe par défaut et représente l’élément courant dans le cas d’une itération. Le code ci-dessus pourrait être réécrit comme ceci :
def myCollection = ["red", "blue"]
def otherCollection = myCollection.findAll { String color -> color == "blue"}
Dans ce dernier cas, on donne explicitement un nom à l’item courant.
Dans le cas où la closure contient beaucoup de lignes, il peut être intéressant d’utiliser cette dernière syntaxe pour la lisibilité.
Ecriture des tests
Cas d’étude
Pour cet exercice, nous allons tester un composant HolidaysHelper qui possède une méthode publique identifyEasterDay qui renvoie pour une année reçue en paramètre le jour de Pâques. Voilà le code de cette méthode telle que je l’ai récupéré sur le net :
//Source : https://www.aveol.fr/?p=608
public LocalDate identifyEasterDay(int year) {
int a = year / 100;
int b = year % 100;
int c = (3 * (a + 25)) / 4;
int d = (3 * (a + 25)) % 4;
int e = (8 * (a + 11)) / 25;
int f = (5 * a + b) % 19;
int g = (19 * f + c - e) % 30;
int h = (f + 11 * g) / 319;
int j = (60 * (5 - d) + b) / 4;
int k = (60 * (5 - d) + b) % 4;
int m = (2 * j - k - g + h) % 7;
int n = (g - h + m + 114) / 31;
int p = (g - h + m + 114) % 31;
int day = p + 1;
int month = n;
return LocalDate.of(year, month, day);
}
Ce code est sans doute perfectible, notamment au niveau de la lisibilité mais avant d’essayer de l’améliorer, nous allons rajouter des tests unitaires.
Premier test
Un test Spock, c’est avant tout une classe qui étend spock.lang.Specification. Même si ce n’est pas obligatoire, l’usage est de suffixer les tests Spock de Spec de la même manière qu’on utilise le suffixe Test pour les tests JUnit.
Voici à quoi peut ressembler un test avec Spock:
def 'easter day in 2014 is 20 april '() {
given:
int year = 2014
LocalDate easterDayExpected = LocalDate.of(2014, Month.APRIL, 20)
HolidaysHelper holidaysHelper = new HolidaysHelper()
when:
LocalDate easterDayActual = holidaysHelper.identifyEasterDay(year)
then:
easterDayActual == easterDayExpected
}
Quelques remarques :
- Notre test est structuré en trois blocs délimités non pas avec des commentaires -tel que je le ferais en JUnit- mais avec des mots clés given-when-then. Ceci est possible grâce à l’aptitude de Groovy à permettre la création de DSL. Pour la lisibilité, cette syntaxe est très intéressante car on distingue immédiatement les différentes parties du test.
- Par défaut, Spock s’attend à retrouver dans le bloc “then” un booléen. Si ce booléen est égal à false à l’exécution, alors le test échouera. Ce mécanisme couplé aux caractéristiques du langage Groovy fait qu’avec Spock, les librairies d’assertion perdent de leur utilité. Chaque ligne du bloc “then” correspond à une assertion. On peut donc enchaîner plusieurs assertions.
- On peut utiliser une phrase comme nom de méthode. Ceci est très utile pour la lisibilité du test. Cependant, il ne faut pas oublier qu’à la compilation, la phrase sera parsée pour obtenir un nom de méthode Java valide. Il faut donc éviter les caractères spéciaux.
- Il est également possible de mettre des descriptifs à chaque bloc afin de faciliter la lisibilité du test :
def 'easter day in 2014 is 20 april '() {
given: 'in 2014'
int year = 2014
LocalDate easterDayExpected = LocalDate.of(2014, Month.APRIL, 20)
HolidaysHelper holidaysHelper = new HolidaysHelper()
when: 'calculate easter day'
LocalDate easterDayActual = holidaysHelper.identifyEasterDay(year)
then: 'the easter day should be correct'
easterDayActual == easterDayExpected
}
Rapport d’erreurs
Reprenons le test précédent afin de le faire échouer. Pour cela, indiquons comme date de Pâques 2014 le 20 Mars au lieu du 20 Avril, qui est la vraie date.
Structure d’un test
Dans l’exemple précédent, nous avons vu un test Spock avec trois blocs given/when/then. Cependant, nos tests peuvent avoir des structures différentes.
Bloc expect
Il est possible de fusionner les blocs when et then en un bloc unique expect. Reprenons le test précédent avec un bloc unique.
def 'easter day in 2014 is 20 april'() {
given:
HolidaysHelper holidaysHelper = new HolidaysHelper()
expect:
LocalDate.of(2014, Month.APRIL, 20) == holidaysHelper.identifyEasterDay(2014)
}
Le bloc given
Le bloc given est optionnel et peut être remplacé par un bloc setup. Par exemple, on aurait pu écrire le test précédent comme suit :
def 'easter day in 2014 is 20 april'() {
expect:
LocalDate.of(2014, Month.APRIL, 20) == new HolidaysHelper().identifyEasterDay(2014)
}
Le bloc where
Le bloc where permet de passer à un jeu de données au test. Nous reviendrons sur ce bloc un peu plus loin.
Jeux de données
Reprenons notre composant précédent : HolidaysHelper et sa méthode identifyEasterDay qui nous renvoie pour une année fournie en entrée le jour de Pâques. Pour valider cet algorithme, il nous faut un jeu de test suffisamment large. Prenons le jeu de données suivant :
{2008, 23 Mars},
{2009, 12 Avril},
{2010, 4 Avril},
{2011, 24 Avril},
{2012, 8 Avril},
{2013, 31 Avril},
{2014, 20 Avril},
{2015, 5 Avril},
{2016, 27 Mars}
Nous allons vérifier par tests unitaires que notre algorithme est correct. Spock nous propose plusieurs alternatives pour l’écriture de ces tests. Cependant, toutes ces alternatives ont quelque chose en commun : le bloc “where”. Ce bloc va se rajouter en fin de test afin de définir les jeux de données.
“Data pipes”
Les data pipes permettent de “connecter une variable à un provider”. En pratique, le provider est une collection ainsi Spock identifie chaque élément de la collection comme un jeu de données de test.
Pour utiliser les “data pipes”, il nous faut définir dans le bloc “where” un tableau pour chaque variable utilisée dans le bloc “expect”. Ci-dessous un exemple :
def 'should calculate easter day for a given year'() {
given:
HolidaysHelper holidaysHelper = new HolidaysHelper()
List<LocalDate> easterDayExpectedList = [LocalDate.of(2008, Month.MARCH, 23),LocalDate.of(2008, Month.MARCH, 23), LocalDate.of(2009, Month.APRIL, 12), LocalDate.of(2010, Month.APRIL, 4), LocalDate.of(2011, Month.APRIL, 24), LocalDate.of(2012, Month.APRIL, 8), LocalDate.of(2013, Month.MARCH, 31), LocalDate.of(2014, Month.APRIL, 20), LocalDate.of(2015, Month.APRIL, 5), LocalDate.of(2016, Month.MARCH, 27)]
List<BigDecimal> yearList = [2008, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016]
expect:
holidaysHelper.identifyEasterDay(year) == easterDayExpected
where:
year << yearList
easterDayExpected << easterDayExpectedList
}
Intéressons-nous à l’instruction:
year << [2008, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016]
Elle nous permet d’indiquer à Groovy que la variable year utilisée dans le bloc “expect” peut prendre chacune des valeurs contenues dans le tableau.
Tableaux de variables
Il s’agit d’un sucre syntaxique pour les “data pipes” : au lieu de fournir explicitement une collection de valeurs, on va plutôt lister ces valeurs à raison d’une ligne par itération.
def 'should calculate easter day for a given year'() {
given:
HolidaysHelper holidaysHelper = new HolidaysHelper()
expect:
holidaysHelper.identifyEasterDay(year) == easterDayExpected
where:
year || easterDayExpected
2008 || LocalDate.of(2008, Month.MARCH, 23)
2008 || LocalDate.of(2008, Month.MARCH, 23)
2009 || LocalDate.of(2009, Month.APRIL, 12)
2010 || LocalDate.of(2010, Month.APRIL, 4)
2011 || LocalDate.of(2011, Month.APRIL, 24)
2012 || LocalDate.of(2012, Month.APRIL, 8)
2013 || LocalDate.of(2013, Month.MARCH, 31)
2014 || LocalDate.of(2014, Month.APRIL, 20)
2015 || LocalDate.of(2015, Month.APRIL, 5)
2016 || LocalDate.of(2016, Month.MARCH, 27)
}
Quelques remarques :
- Le “||” permet de séparer les paramètres entrant des valeurs de retour de la fonction testée. S’il y avait eu plusieurs paramètres en entrée de la fonction identifyEasterDay, nous les aurions séparés par un ‘|’. Cependant cette distinction entre paramètres de sortie et d’entrée, si elle est utile pour la lisibilité, n’est pas indispensable. On peut en effet utiliser un simple ‘|’ comme séparateur pour toutes les variables. Personnellement, j’ai une préférence pour l’utilisation des “data tables” avec l’emploi du “||” car je trouve que cela aide à la lisibilité des tests.
- le bloc “where” doit comporter au moins une variable. Dans le cas particulier où il n’y aurait qu’une variable unique, on va utiliser le caractère “_” pour simuler la présence d’une seconde variable. Nous verrons plus loin avec les mocks que, pour Spock, le caractère _ signifie “n’importe quoi”. Ci-dessous, un exemple extrait de la doc de Spock
where: a | _ 1 | _
Noms de tests dynamiques
Reprenons le test précédent et modifions un des jours de Pâques de façon à faire échouer le test. Par exemple, mettons que le jour de Pâques en 2014 doit être égal au 1er Avril et non au 20 Avril. Ci-dessous, le screenshot de l’exécution de ce test dans mon IDE.
On peut constater que si Spock m’indique certes le test en erreur, il ne m’indique pas explicitement quel jeu a échoué. Pour savoir que c’est l’année 2014 qui est en cause, je dois regarder dans les logs. Il est souhaitable, en particulier si les tests sont lancés sur un serveur d’intégration continue, qu’on puisse identifier au premier coup d’œil quel jeu de données a fait échouer le test. C’est ce à quoi va nous aider l’annotation @Unroll.
Grace à celle-ci, nous pouvons insérer dans le nom du test des expressions qui seront évaluées par le framework. En pratique, ces expressions sont préfixées du caractère “#” et peuvent être:
- l’appel d’une méthode sans argument
- une variable
Réécrivons le test précédent avec l’annotation @Unroll :
@Unroll
def "in #year easter day is #easterDay"() {
given :
HolidaysHelper holidaysHelper = new HolidaysHelper()
expect:
easterDay == holidaysHelper.identifyEasterDay(year)
where:
year << [2008, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016]
easterDay << [LocalDate.of(2008, Month.MARCH, 23),LocalDate.of(2008, Month.MARCH, 23), LocalDate.of(2009, Month.APRIL, 12), LocalDate.of(2010, Month.APRIL, 4), LocalDate.of(2011, Month.APRIL, 24), LocalDate.of(2012, Month.APRIL, 8), LocalDate.of(2013, Month.MARCH, 31), LocalDate.of(2014, Month.APRIL, 20), LocalDate.of(2015, Month.APRIL, 5), LocalDate.of(2016, Month.MARCH, 27)]
}
Lançons le test ainsi modifié :
On peut voir que Spock a généré un nom pour chaque jeu de test en interpolant les variables que nous avons placées dans le nom. Ainsi, nous pouvons identifier quel jeu de données met en échec le test.
Assertions
Nos tests Spock étant écrits en Groovy, nous pouvons, en exploitant les API natives du langage, nous passer d’une libraire d’assertion. En effet, ces librairies étaient quasi-indispensables en Java (avant la version 8) parce que le langage présentait certaines limitations. Pour illustrer les assertions avec Groovy, nous allons prendre un use case récurrent : la manipulation des listes.
Cas d’étude
Imaginons que nous avons une classe Author avec trois propriétés prénom, nom et année de naissance. Supposons que nous avons une méthode de type DAO qui nous renvoie une liste d’objet Author.
List<Author> findAllAuthorsInRepository() {
//Va récupérer les données en BD
}
Ci-dessous le contenu de la liste renvoyée par la méthode findAllAuthorsInRepository():
- Robert, Ludlum, 1927
- Paul, Auster, 1947
- Jean, Pliya, 1931
- Émile, Zola, 1840
- Stefan, Zweig, 1881
- Tom, Clancy, 1947
- Jean-Marie Gustave, Le Clézio, 1940
- Florent, Couao-Zotti, 1964
- Jean, D’Ormesson, 1925
- Robert Louis, Stevenson, 1850
- Isaac, Asimov, 1920
Nous allons effectuer différentes assertions sur cette liste récupérée.
Assertions avec Spock/Groovy
- Vérifions que nous avons deux auteurs qui sont nés en 1947 :
authors.findAll { it.year == Year.of(1947) }.size() == 2
- Vérifions qu’il y a au moins deux auteurs qui sont nés la même année
authors.collect { it.year.getValue() }.unique().size() < authors.size()
- Vérifions qu’aucun auteur ne s’appelle “King”:
authors.findAll { it.lastName == 'King' }.isEmpty()
- Nous aurions pu écrire l’assertion précédente comme suit :
authors.find { it.lastName == 'King' } == null
- Vérifions que le “dernier” auteur par ordre alphabétique du nom est “Zweig”
authors.sort { it.lastName }.last().lastName == 'Zweig'
- Supposons que, dans un cas particulier, notre service doive renvoyer une exception DataAccessException. Vérifions que cette exception est réellement levée :
thrown(DataAccessException)
Il existe une seconde syntaxe qui permet d’effectuer des assertions sur l’exception qui a été levée :
DataAccessException ex = thrown() ex.message == 'message attendu'
On peut également vérifier qu’une exception particulière n’est pas levée :
notThrown(DataAccessException)
Mocks
Dans cet article, je parle des mocks au sens large, sans chercher à distinguer Dummy, Fake, Stubs ou Mocks. En effet, au quotidien, par abus de langage, je ne fais pas cette distinction même si elle est intéressante pour la compréhension. Si vous souhaitez en savoir plus sur les différences entre bouchon et mocks, je vous invite à lire l’article de Martin Fowler sur le sujet.
Cas d’étude
Je vous propose un cas classique, afin de comprendre comment on manipule les mocks avec Spock. Supposons que nous devons gérer un processus d’achat en ligne avec paiement uniquement par carte bancaire. Nous disposons d’un composant qui effectue le paiement, c’est à dire :
- appel au web service du partenaire financier pour débiter la carte bancaire
- gestion des erreurs de l’appel afin qu’elles ne remontent pas
- traitement de la réponse du web service du partenaire afin d’envoyer à l’appelant un objet de type PaymentResult
L’objet PaymentResult renvoyé contient l’id de transaction renvoyé par le web-service distant lorsque la carte du client a été débitée.
Par ailleurs, nous avons un service OrderService qui permet de valider des commandes. La validation de la commande consiste à :
- débiter la carte bleue du client
- mettre à jour la commande avec l’id de transaction et un nouveau statut “Payé”
Voici à quoi pourrait ressembler la méthode validateOrder de la classe OrderService
public Order validateOrder(final PaymentCard paymentCard, final Order order) throws PaymentFailedException {
final String internalReference = order.getReference();
final float amount = order.getAmount();
//Appel du composant externe de paiement (exemple PAYBOX)
PaymentResult paymentResult = paymentService.makePayment(paymentCard, internalReference, amount);
//Si le paiement échoue, on arrête le processus
if (paymentResult.isFailed()) {
throw new PaymentFailedException();
}
//Si le paiement réussit, on met à jour la ccommande
return updateOrderWhenPaymentIsdone(order, paymentResult);
}
La méthode validateOrder a besoin du composant PaymentService pour fonctionner. Pour tester validateOrder unitairement, nous devons pouvoir mocker / simuler le composant PaymentService.
Création d’un mock
Avec Spock, un mock est obtenu grâce à la méthode statique Mock de la classe spock.lang.MockingApi. Il y a deux syntaxes équivalentes :
PaymentService paymentService = Mock()
ou bien
def paymentService = Mock(PaymentService)
Une fois le mock créé comme ci-dessus, il nous faut définir son comportement. En effet, par défaut, les méthodes du mock vont renvoyer la valeur par défaut c’est à dire zéro, null, ou false selon le type de retour attendu.
Par exemple, ci-dessous nous créons un mock de PaymentService qui va simuler une réponse positive du web-service distant.
PaymentResult paymentResultSuccess = new PaymentResult('MASTER CARD TRANSACTION ID')
String internalReference = 'azerty-17'
float amount = 25f
PaymentCard paymentCard = buildPaymentCard()
PaymentService paymentService = Mock() {
1 * makePayment(paymentCard, internalReference, amount) >> paymentResultSuccess
}
Au lieu de définir notre mock en un bloc à la manière d’une classe anonyme, nous aurions pu écrire
PaymentService paymentService = Mock()
1 * paymentService.makePayment(paymentCard, internalReference, amount) >> paymentResultSuccess
Définition du nombre d’appels attendus
Reprenons le bloc de code précédent et étudions l’instruction :
1 * makePayment(paymentCard, internalReference, amount) >> paymentResultSuccess
Cette ligne permet de définir la méthode makePayment pour notre Mock.
Le chiffre “1” au début de la ligne nous permet de d’indiquer que la méthode “makePayment” devra être appelée exactement une fois. Si à l’exécution, nous avions deux appels, le test échouerait. Le caractère “*” indique le début de la définition de la méthode. Il sera donc présent quel que soit la cardinalité. Nous pouvons aussi définir le nombre d’appels sous forme d’intervalle. Par exemple, si nous nous attendons à avoir entre 2 et 7 appels, nous aurions écrit :
(2,7) * makePayment(paymentCard, internalReference, amount) >> paymentResultSuccess
Enfin, le joker “_” que nous avions vu précédemment est valable ici. Ainsi, si nous escomptions au moins un appel, nous écrions :
(1,_) * makePayment(paymentCard, internalReference, amount) >> paymentResultSuccess
Définition de la valeur de retour
Dans notre bloc de code ci-dessus, l’opérateur >> est équivalent au return. Il permet de définir la valeur retournée.
Dans notre cas, l’objet de retour était un objet défini plus haut mais nous pouvons également déclarer une closure et définir un comportement “plus complexe”.
1 * makePayment(paymentCard, internalReference, amount) >> {new PaymentResult('MASTER CARD TRANSACTION ID') }
De la même façon, si nous souhaitons que le mock lève une exception, nous pourrions écrire :
1 * makePayment(paymentCard, internalReference, amount) >> {throw new NullPointerException()}
Définition des arguments attendus
Nous pouvons indiquer avec quels arguments exactement notre méthode doit être appelée. Ainsi, si d’autres arguments que ceux-là sont employés, le test échouera. C’est ce que nous avions ici :
1 * makePayment(paymentCard, internalReference, amount) >> paymentResultSuccess
Mais nous pourrions aussi souhaiter que la méthode makePayment du mock renvoie un résultat “positif” quel que soit la référence et le montant passés en paramètres. Dans ce cas, nous aurions remplacé les variables internalReference et amount par le joker “_”.
1 * makePayment(paymentCard, _, _) >> paymentResultSuccess
Grace aux spécificités de Groovy, il est possible de remplacer un paramètre par une condition, une closure qui va définir les critères que doit respecter le paramètre. Par exemple, pour indiquer que tous les float sont autorisés comme montant, nous pourrions écrire :
1 * makePayment(paymentCard, _, _ as Float) >> paymentResultSuccess
Conclusion
JUnit et TestNG ne sont pas près d’être jetés aux oubliettes. Cependant il peut être intéressant, si on en a l’occasion, d’expérimenter Spock sur nos projets. D’autant qu’il peut être intégré dans un projet Java – JUnit.