🎬 Passer en mode présentation
# Junit Comment facilité la conception, l'écriture et la compréhension des tests unitaires ---
# Les tests unitaires # Bonne pratiques # Gherkin # Mockito # Simplifier l'écriture des tests # Mise en place
--- ## Les tests unitaires  ---  ---  ---  ---  --- ## Bonnes pratiques - Choix à faire lors de l’écriture d’un test: - Manipulation d’objets complexes => initialiser le minimum de ce dont on a besoin pour satisfaire le test. - Risque calculé de ce que je veux tester par rapport à l’exhaustivité des tests - Capitaliser les mocks ‘utilitaires’ (ex : nomenclature). - Ecrire un TU - Commencer par le test nominal - Décrire le test avec gherkin (given…when…then) - Ecrire de la même façon les autres tests - Capitaliser les jeux tests d’un service si possible (un jeu de test pour tous les tests d’un même service) - Faire test nominal en initialisant les données nécessaires. Repartir systématiquement de ces données en les dégradant suivant les besoins pour les autres testspour les tests - Bug / Régression - On cherche et complète le given / then OU ajoute un TU. - Plus de tests à écrire mais tests beaucoup plus simple à comprendre, à écrire et à maintenir - On n’a plus besoin de tester des méthodes private ---  --- ## Gherkin ### Historique - Ce langage est conçu pour être compréhensible par tous les acteurs du projet, y compris le non-technique. Il permet d'écrire des descriptions de fonctionnalités sous forme de phrases simples et naturelles en français. - Issu du Behavior-Driven Development (BDD), car il permet d'exprimer la façon dont les utilisateurs interagissent avec le système, ce qui aide à définir les exigences du projet de manière claire et précise. - US : En tant que … je veux …afin - TA : Etant donnée …lorsque …alors (given …when …then) Etant donnée que je saisie - Peut être utilisé dans les tests unitaires afin de coder les tests tout en les décrivant. - Présent dans Mockito --- ### Principe - Given : création des inputs - Initialisation des paramètres de la méthode - Initialisation des mocks => thenReturn spécifique au TU ou thenAnswer générique suivant comment il est appelé - When : quelle méthode testons nous ? - Souvent une seule instruction mais permet d’identifier en un coup d’œil la méthode testée - Then : Comment vérifier que le test est ok ? - Verify() on a bien appelé les services mockées - assertThat() : sur le retour du service ou sur les paramètres à des mocks (Captor) --- ### première itération  --- ### deuxième itération - Inconvénients - Demande de la rigueur dans la description des tests - Pas de ligne directrice pour faciliter l'écriture des tests - Améliorations possibles - Diriger le développeur dans l'écriture du test Il existe des librairies qui permettent de faciliter l'écriture des tests unitaires en utilisant le gherkin mais je voulais rester dans la simplicité surtout que ce n'est pas pour le moment une volonté générale dans l'équipe. => Limiter les dépendances et l'ajout de librairies J'ai donc décidé de mettre en place le minimum de ce que je voulais normaliser, à savoir écire les tests comme on peut le faire avec les lambdas : ``` java Gherkin.given() .when() .then(); ``` --- ### Conception d'une librairie Gherkin ```puml @startuml Title Package Gherkin ' Styles skinparam linetype ortho skinparam classAttributeIconSize 0 skinparam shadowing false skinparam classFontColor #ffffff skinparam interfaceFontColor #ffffff skinparam classAttributeFontColor #ffffff skinparam fontColor #ffffff skinparam classBackgroundColor #21252d skinparam classBorderColor #4f5f72 skinparam interfaceBackgroundColor #21252d skinparam interfaceBorderColor #4f5f72 skinparam ArrowColor #4c566a ' Interfaces interface GivenStep { + and(action: ThrowingRunnable): GivenStep + when(action: ThrowingSupplier
): WhenStep
+ when(action: ThrowingRunnable): WhenStep
} interface WhenStep
{ + then(assertion: ThrowingConsumer
): ThenStep
+ then(assertion: ThrowingRunnable): ThenStep
+ thenException(exceptionClass: Class extends Throwable>): ExceptionStep
} interface ThenStep
{ + andThen(assertion: ThrowingConsumer
): ThenStep
+ andThen(assertion: ThrowingRunnable): ThenStep
+ andThen(assertion: ThrowingFunction
): ThenStep
} interface ExceptionStep
{ + withMessage(message: String): ExceptionStep
} ' Functional Interfaces interface ThrowingRunnable { + execute(): void } interface ThrowingSupplier
{ + get(): T } interface ThrowingConsumer
{ + accept(t: T): void } interface ThrowingFunction
{ + apply(t: T): U } ' Classes class Scenario { {static} + given(action: ThrowingRunnable): ScenarioGiven {static} + GivenStep, WhenStep, ThenStep, ExceptionStep } class ScenarioGiven { - givens: List
+ given(action: ThrowingRunnable): ScenarioGiven + and(action: ThrowingRunnable): GivenStep + when(action: ThrowingSupplier
): WhenStep
+ when(action: ThrowingRunnable): WhenStep
} class ScenarioWhen
{ - givens: List
- whenThrowingSupplier: ThrowingSupplier
+ then(assertion: ThrowingConsumer
): ThenStep
+ then(assertion: ThrowingRunnable): ThenStep
+ thenException(expected: Class extends Throwable>): ExceptionStep
} class ScenarioThen
{ - result: R - assertions: List
> + andThen(assertion: ThrowingConsumer
): ThenStep
+ andThen(assertion: ThrowingRunnable): ThenStep
+ andThen(assertion: ThrowingFunction
): ThenStep
} class ScenarioException
{ + withMessage(message: String): ExceptionStep
} class AbstractScenarioTest { # scenarioTestWatcher: ScenarioTestWatcher + beforeEachTest(): void } class ScenarioTestWatcher { - results: List
+ testSuccessful(context: ExtensionContext): void + testFailed(context: ExtensionContext, cause: Throwable): void + testAborted(context: ExtensionContext, cause: Throwable): void + testDisabled(context: ExtensionContext, reason: Optional
): void + afterTestExecution(context: ExtensionContext): void } ' Relations ScenarioGiven ..|> GivenStep ScenarioWhen ..|> WhenStep ScenarioThen ..|> ThenStep ScenarioException ..|> ExceptionStep ScenarioGiven --> ScenarioWhen : creates > ScenarioWhen --> ScenarioThen : creates > ScenarioWhen --> ScenarioException : creates > Scenario --> ScenarioGiven : uses Scenario --> ScenarioWhen : uses Scenario --> ScenarioThen : uses Scenario --> ScenarioException : uses AbstractScenarioTest --> ScenarioTestWatcher : has > @enduml ``` --- ### Utilisation ```java public class MonTest extends AbstractScenarioTest { private Service service; private ServiceMock serviceMock; private Donnee donnee; @Test @ScenarioTest(description = "Test nominal avec toutes les méthodes ") public void testSimple() { Scenario.given(this::initServices) .and(this::maDonnee) .when(this::executeService) .then(this::checkResult) .andThen(this::checkEnregistrement); } @Test @ScenarioTest(description = "Test retournant une exception avec validation du message") public void testException() { Scenario.given(this::initServices) .and(this::maDonneeNull) .when(this::executeServiceWithException) .thenException(IllegalArgumentException.class) .withMessage("L'argument est erroné"); } private Object executeService(Object parametre){ return service.execute(parametre); } private void checkResult(Object result){ assertEquals(result, "resultat attendu"); } private void checkEnregistrement(){ verify(mockService,times(1)).execute(any()); } } ``` --- ### Affichage des résultats - --- #
Terminé