Introduction au comportement Lambda

Introduction au comportement Lambda

1. Vue d'ensemble

Dans cet article, nous aborderons un nouveau cadre de test basé sur Java appeléLambda Behave.

Comme son nom l'indique, ce cadre de test est conçu pour fonctionner avec Java 8 Lambdas. En outre, dans cet article, nous examinerons les spécifications et verrons un exemple pour chacune.

La dépendance Maven que nous devons inclure est la suivante:


    com.insightfullogic
    lambda-behave
    0.4

La dernière version peut être trouvéehere.

2. Les bases

L'un des objectifs du cadre est d'obtenir une grande lisibilité. La syntaxe nous encourage à décrire des cas de test en utilisant des phrases complètes plutôt que quelques mots.

Nous pouvons tirer parti des tests paramétrés et lorsque nous ne voulons pas lier les cas de test à certaines valeurs prédéfinies, nous pouvons générer des paramètres aléatoires.

3. Implémentation du test Lambda Behave

Chaque suite de spécifications commence parSuite.describe. À ce stade, nous avons plusieurs méthodes intégrées pour déclarer notre spécification. Ainsi, unSuite est comme une classe de test JUnit, et les spécifications sont comme les méthodes annotées avec@Test dans JUnit.

Pour décrire un test, nous utilisonsshould(). De même, si nous nommons le paramètre lambda d'attente comme“expect”,, nous pourrions dire quel résultat nous attendons du test, parexpect.that().

Si nous voulons configurer ou supprimer des données avant et après une spécification, nous pouvons utiliserit.isSetupWith() etit.isConcludedWith(). De la même manière, pour faire quelque chose avant et après lesSuite, nous ' utiliseronsit.initiatizesWith() etit.completesWith().

Voyons un exemple de spécification de test simple pour la classeCalculator:

public class Calculator {

    public int add() {
        return this.x + this.y;
    }

    public int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException();
        }
        return a / b;
    }
}

Nous allons commencer parSuite.describe puis ajouter le code pour initialiser lesCalculator.

Ensuite, nous allons tester la méthodeadd() en écrivant une spécification:

{
    Suite.describe("Lambda behave example tests", it -> {
        it.isSetupWith(() -> {
            calculator = new Calculator(1, 2);
        });

        it.should("Add the given numbers", expect -> {
            expect.that(calculator.add()).is(3);
        });
}

Ici, nous avons nommé les variables“it” et“expect” pour une meilleure lisibilité. S'agissant de noms de paramètres lambda, nous pouvons les remplacer par tous les noms de notre choix.

Le premier argument deshould() décrit en utilisant un anglais simple, ce que ce test doit vérifier. Le deuxième argument est un lambda, qui indique notre espérance que la méthodeadd() doit retourner 3.

Ajoutons un autre scénario de test pour la division par 0 et vérifions si nous obtenons une exception:

it.should("Throw an exception if divide by 0", expect -> {
    expect.exception(ArithmeticException.class, () -> {
        calculator.divide(1, 0);
    });
});

Dans ce cas, nous attendons une exception, donc nous déclaronsexpect.exception() et à l'intérieur de cela, nous écrivons le code qui devrait lever une exception.

Notez que la description textuelle doit être unique pour chaque spécification.

4. Spécifications basées sur les données

Ce cadre permet un paramétrage de test au niveau de la spécification.

Pour créer un exemple, ajoutons une méthode à notre classeCalculator:

public int add(int a, int b) {
    return a + b;
}

Écrivons un test basé sur les données:

it.uses(2, 3, 5)
  .and(23, 10, 33)
  .toShow("%d + %d = %d", (expect, a, b, c) -> {
    expect.that(calculator.add(a, b)).is(c);
});

La méthodeuses() est utilisée pour spécifier les données d'entrée dans différents nombres de colonnes. Les deux premiers arguments sont les paramètres de la fonctionadd() et le troisième est le résultat attendu. Ces paramètres peuvent également être utilisés dans la description, comme indiqué dans le test.

toShow() est utilisé pour décrire le test à l'aide des paramètres - avec la sortie suivante:

0: 2 + 3 = 5 (seed: 42562700892554)(Lambda behave example tests)
1: 23 + 10 = 33 (seed: 42562700892554)(Lambda behave example tests)

5. Spécifications générées - Test basé sur la propriété

Habituellement, lorsque nous écrivons un test unitaire, nous souhaitons nous concentrer sur des propriétés plus larges, valables pour notre système.

Par exemple, lorsque nous testons une fonction d'inversionString, nous pourrions vérifier que si nous inversons deux fois unString particulier, nous finirons avec lesString. d'origine

Property-Based testing focuses on the generic property without hard-coding specific test parameters. Nous pouvons y parvenir en utilisant des cas de test générés aléatoirement.

Cette stratégie est similaire à l'utilisation de spécifications pilotées par les données, mais au lieu de spécifier le tableau de données, nous spécifions le nombre de scénarios de test à générer.

Ainsi, notre test basé sur la propriété d'inversion deString ressemblerait à ceci:

it.requires(2)
  .example(Generator.asciiStrings())
  .toShow("Reversing a String twice returns the original String",
    (expect, str) -> {
        String same = new StringBuilder(str)
          .reverse().reverse().toString();
        expect.that(same).isEqualTo(str);
   });

Nous avons la méthode_ indicated the number of required test cases using the _requires(). Nous utilisons la clauseexample() pour indiquer le type d'objets dont nous avons besoin et comment.

Le résultat pour cette spécification est:

0: Reversing a String twice returns the original String(ljL+qz2)
  (seed: 42562700892554)(Lambda behave example tests)
1: Reversing a String twice returns the original String(g)
  (seed: 42562700892554)(Lambda behave example tests)

5.1. Génération de cas de test déterministe

Lorsque nous utilisons les cas de test générés automatiquement, il devient assez difficile d’isoler les échecs de test. Par exemple,if our functionality fails once in 1000 times, a specification that auto-generates just 10 cases, will have to be run over and over to observe the error.

Nous avons donc besoin de la capacité de réexécuter de manière déterministe les tests, y compris les cas précédemment échoués.

Lambda Behave est capable de gérer ce problème. Comme indiqué dans le résultat du scénario de test précédent, il imprime le germe utilisé pour générer l'ensemble aléatoire de scénarios de test. Donc, si quelque chose échoue,we can use the seed to re-create previously generated test cases.

Nous pouvons regarder la sortie du cas de test et identifier la graine:(seed: 42562700892554). Maintenant, pour générer à nouveau le même ensemble de tests, nous pouvons utiliser lesSourceGenerator.

LeSourceGenerator contient la méthodedeterministicNumbers() qui prend juste la valeur de départ comme argument:

 it.requires(2)
   .withSource(SourceGenerator.deterministicNumbers(42562700892554L))
   .example(Generator.asciiStrings())
   .toShow("Reversing a String twice returns the original String",
     (expect, str) -> {
       String same = new StringBuilder(str).reverse()
         .reverse()
         .toString();
       expect.that(same).isEqualTo(str);
});

En exécutant ce test, nous obtiendrons le même résultat que nous l'avons vu précédemment.

6. Conclusion

Dans cet article, nous avons vu comment écrire des tests unitaires à l'aide des expressions lambda Java 8, dans un nouveau cadre de tests de fluence appelé Lambda Behave.

Comme toujours, le code de ces exemples peut être trouvéover on GitHub.