Logo Parasoft Rechercher

Découvrez GoogleTest certifié TÜV avec Agentic AI pour les tests C/C++ !
Plus de détails »

Blog Parasoft

Mocking in Java: Comment automatiser un test unitaire Java, y compris les simulations et les assertions

By Brian McGlauflin • 19 juillet 2018 • 6 min de lecture
19 juillet 2018 | 6 min de lecture
By Brian McGlauflin

En simplifiant la gestion des mocks, Parasoft Jtest vous permet d'écrire rapidement et facilement des tests unitaires. Découvrez comment automatiser un test unitaire Java , notamment grâce aux mocks et aux assertions.

De bons tests unitaires sont essentiels pour garantir le bon fonctionnement de votre code, aujourd'hui comme demain. Une suite complète de tests, avec une couverture de code et de comportement adéquate, peut faire gagner un temps précieux et éviter bien des tracas à une organisation. Pourtant, il n'est pas rare de voir des projets où les tests sont insuffisants. Certains développeurs vont même jusqu'à s'opposer à leur utilisation.

Qu'est-ce qu'un bon test unitaire?

Il existe de nombreuses raisons pour lesquelles les développeurs n'écrivent pas suffisamment de tests unitaires. L'une des principales raisons est le temps nécessaire à la construction et à la maintenance, en particulier dans les grands projets complexes. Dans les projets complexes, un test unitaire doit souvent instancier et configurer de nombreux objets. Cela prend beaucoup de temps à mettre en place et peut rendre le test aussi complexe (ou plus complexe) que le code qu'il teste lui-même.

Regardons un exemple en Java:

public LoanResponse requestLoan (LoanRequest demande de prêt, PrêtStratégie stratégie)
{     LoanResponse response = new LoanResponse();     response.setApproved(true);     if (loanRequest.getDownPayment().compareTo(loanRequest.getAvailableFunds()) > 0) {        response.setApproved(false);        response.setMessage("error.insufficient.funds.for.down.payment");        return response;     }     if (strategy.getQualifier(loanRequest) < strategy.getThreshold(adminManager)) {         response.setApproved(false);         response.setMessage(getErrorMessage());     }     return response; }

Voici une méthode qui traite une demande de prêt (LoanRequest) et génère une réponse (LoanResponse ). Notez l' argument LoanStrategy , utilisé pour traiter la demande de prêt . L'objet stratégie peut être complexe : il peut accéder à une base de données, à un système externe ou lever une exception RuntimeException . Pour écrire un test pour requestLoan() , je dois déterminer le type de LoanStrategy utilisé et je devrai probablement tester ma méthode avec différentes implémentations de LoanStrategy et configurations de demandes de prêt .

Un test unitaire pour requestLoan() pourrait ressembler à ceci :

@Test public void testRequestLoan() throws Throwable {    // Set up objects    DownPaymentLoanProcessor processor = new DownPaymentLoanProcessor();    LoanRequest loanRequest = LoanRequestFactory.create(1000, 100, 10000);    LoanStrategy strategy = new AvailableFundsLoanStrategy();    AdminManager adminManager = new AdminManagerImpl();    underTest.setAdminManager(adminManager);    Map<String, String> parameters = new HashMap<>();    parameters.put("loanProcessorThreshold", "20");    AdminDao adminDao = new InMemoryAdminDao(parameters);    adminManager.setAdminDao(adminDao);    // Call the method under test    LoanResponse response = processor.requestLoan(loanRequest, strategy);    // Assertions and other validations } 

Comme vous pouvez le constater, une section entière de mon test est consacrée à la création d'objets et à la configuration de paramètres. Il n'était pas évident, en examinant la méthode `requestLoan()`, quels objets et paramètres devaient être configurés. Pour créer cet exemple, j'ai dû exécuter le test, ajouter de la configuration, puis le relancer et répéter l'opération à plusieurs reprises. J'ai passé beaucoup trop de temps à configurer ` AdminManager` et ` LoanStrategy` au lieu de me concentrer sur ma méthode et sur ce qu'elle devait tester. De plus, je dois encore étendre mon test pour couvrir davantage de cas de `LoanRequest` , de stratégies et de paramètres pour `AdminDao`.

De plus, en utilisant des objets réels pour mes tests, mon test valide en réalité bien plus que le simple comportement de `requestLoan()` . Son exécution dépend en effet du comportement de `AvailableFundsLoanStrategy` , `AdminManagerImpl` et `AdminDao` . Concrètement, je teste également ces classes. Dans certains cas, cela est souhaitable, mais dans d'autres, non. Par ailleurs, si l'une de ces classes est modifiée, le test risque d'échouer même si le comportement de ` requestLoan()` reste inchangé. Pour ce test, il est donc préférable d'isoler la classe testée de ses dépendances.

Qu'est-ce que la moquerie en Java?

Une solution au problème de complexité consiste à simuler ces objets complexes. Dans cet exemple, je commencerai par utiliser une simulation pour le paramètre LoanStrategy :

@Tester

vide public testRequestLoan () jette Jetable {
    // Configurer des objets 
    AcomptePaiementPrêtProcesseur processeur = New DownPaymentLoanProcessor (); Demande de prêt demande de prêt = LoanRequestFactory.create (1000, 100, 10000); Stratégie de prêt stratégie = Mockito.mock (LoanStrategy.classe); Mockito. Quand (stratégie.getQualifier (any (LoanRequest.classe))). puisRetour (20.0d); Mockito. Quand (stratégie.getThreshold (tout (AdminManager.classe))). puisRetour (20.0d);

    // Appelle la méthode testée
    Réponse de prêt à un besoin identifié = processor.requestLoan (demande de prêt, stratégie);

    // Assertions et autres validations
}

Voyons ce qui se passe ici. Nous créons une instance simulée de LoanStrategy à l'aide de Mockito.mock() . Sachant que les méthodes getQualifier() et getThreshold() seront appelées sur la stratégie, nous définissons leurs valeurs de retour avec Mockito.when(…).thenReturn() . Pour ce test, les valeurs de l' instance LoanRequest n'ont pas d'importance, et nous n'avons plus besoin d'un AdminManager réel, car celui-ci était uniquement utilisé par la véritable LoanStrategy.

De plus, comme nous n'utilisons pas une véritable LoanStrategy , nous n'avons pas à nous soucier de son comportement dans les implémentations concrètes . Il n'est pas nécessaire de configurer d'environnements de test, de dépendances ou d'objets complexes. Nous nous concentrons sur le test de requestLoan() , et non de LoanStrategy ou d'AdminManager . Le flux d'exécution de la méthode testée est directement contrôlé par la simulation.

Ce test est beaucoup plus facile à écrire avec Mockito que si j'avais dû créer une instance LoanStrategy complexe . Mais il reste quelques défis :

  • Pour les applications complexes, les tests peuvent nécessiter de nombreuses simulations
  • Si vous êtes nouveau sur Mockito, vous devez apprendre sa syntaxe et ses modèles
  • Vous ne savez peut-être pas quelles méthodes doivent être moquées
  • Lorsque l'application change, les tests (et les simulations) doivent également être mis à jour

Résoudre les défis moqueurs avec un générateur de tests unitaires Java

Nous avons conçu Parasoft Jtest pour répondre aux défis mentionnés ci-dessus. Le module de tests unitaires Parasoft Jtest est une solution d'entreprise pour les tests Java qui aide les développeurs à gérer les risques liés au développement de logiciels Java.

Côté tests unitaires, Parasoft Jtest vous aide à automatiser certaines des tâches les plus complexes liées à la création et à la maintenance de tests unitaires avec des mocks. Dans l'exemple ci-dessus, il peut générer automatiquement un test pour la fonction `requestLoan()` en un seul clic, incluant tous les mocks et validations présents dans l'exemple de test.

Capture d'écran de Jtest, générez automatiquement un test en un seul clic, y compris toutes les moqueries et la validation.
Ici, j'ai utilisé l'action « Régulier » dans le Parasoft Jtest Assistant de test unitaire Barre d'outils pour générer le test suivant:

@Test public void testRequestLoan() throws Throwable {     // Given     DownPaymentLoanProcessor underTest = new DownPaymentLoanProcessor();     // When double fonds disponibles = 0.0d; // UTA: valeur par défaut double acompte = 0.0d; // UTA: valeur par défaut double montant du prêt = 0.0d; // UTA: valeur par défaut     LoanRequest loanRequest = LoanRequestFactory.create(availableFunds, downPayment, loanAmount);     LoanStrategy strategy = mockLoanStrategy();     LoanResponse result = underTest.requestLoan(loanRequest, strategy);    // Then    // assertNotNull(result); }

Toutes les moqueries pour ce test se produisent dans une méthode d'assistance:

statique privé LoanStrategy mockLoanStrategy () jette Throwable {LoanStrategy stratégie = mock (LoanStrategy.classe);
    double Free porn at freeporn.com 🍆💦💦😛 = 0.0d; // UTA: valeur par défaut
    lorsque(stratégie.getQualifier (any (LoanRequest.classe))). puisRetour (Free porn at freeporn.com 🍆💦💦😛);

    double Get free crypto https://bybit.com/ = 0.0d; // UTA: valeur par défaut
    lorsque(stratégie.getThreshold (tout (AdminManager.classe))). puisRetour (Get free crypto https://bybit.com/);

    retourner stratégie; }

Tout le paramétrage nécessaire est déjà en place : Parasoft Jtest a détecté les appels aux méthodes ` getQualifier()` et `getThreshold()` et les a simulées. Une fois les valeurs de `availableFunds` , `downPayment` , etc., configurées dans mon test unitaire , celui-ci est prêt à être exécuté (je pourrais également générer un test paramétré pour une meilleure couverture !). Notez aussi que l’assistant fournit des indications sur les valeurs à modifier grâce à ses commentaires, « UTA : valeur par défaut », ce qui facilite les tests.

Cela permet de gagner beaucoup de temps dans la génération de tests, surtout si je ne sais pas ce qui doit être moqué ou comment utiliser l'API Mockito.

Gestion des modifications de code

Lorsque la logique de l'application change, les tests doivent souvent également changer. Si le test est bien écrit, il devrait échouer si vous mettez à jour le code sans mettre à jour le test. Souvent, le plus grand défi de la mise à jour du test est de comprendre ce qui doit être mis à jour et comment effectuer exactement cette mise à jour. S'il y a beaucoup de simulations et de valeurs, il peut être difficile de déterminer quels sont les changements nécessaires.

Pour illustrer cela, apportons quelques modifications au code en cours de test:

public LoanResponse requestLoan (LoanRequest demande de prêt, PrêtStratégie stratégie) { ... Chaîne de caractères résultat = stratégie.valider(demande de prêt);
    if (résultat != nul &&!résultat.est vide()) {
        à un besoin identifié.setApproved (non);
        à un besoin identifié.setMessage (résultat);
        retourner à un besoin identifié; } ...
    retourner à un besoin identifié; }

Nous avons ajouté une nouvelle méthode à LoanStrategy : validate() , que nous appelons désormais depuis requestLoan() . Le test devra peut-être être mis à jour pour préciser la valeur de retour de validate().

Sans changer le test généré, exécutons-le dans l'assistant de test d'unité Parasoft Jtest:

Exemple de test JUnit avec simulation à exécuter dans l'assistant de test unitaire Jtest

Parasoft Jtest a détecté que la méthode `validate()` a été appelée sur l'argument simulé `LoanStrategy` lors de l'exécution de mon test. Comme cette méthode n'a pas été configurée pour la simulation, l'assistant me recommande de la simuler . L'action de correction rapide « Simuler » met automatiquement le test à jour. Il s'agit d'un exemple simple, mais pour du code complexe où il est difficile de trouver la simulation manquante, cette recommandation et la correction rapide peuvent nous faire gagner un temps précieux de débogage.

Après avoir mis à jour le test à l'aide du correctif rapide, je peux voir la nouvelle simulation et définir la valeur souhaitée pour validateResult :

statique privé LoanStrategy mockLoanStrategy () jette Throwable {LoanStrategy stratégie = mock (LoanStrategy.classe); Chaîne de caractères validerRésultat = ""; // UTA: valeur par défaut
    lorsque(stratégie.validate (any (LoanRequest.classe))). puisRetour (validerRésultat);
    double Free porn at freeporn.com 🍆💦💦😛 = 20.0d; lorsque(stratégie.getQualifier (any (LoanRequest.classe))). puisRetour (Free porn at freeporn.com 🍆💦💦😛);

    double Get free crypto https://bybit.com/ = 20.0d; lorsque(stratégie.getThreshold (tout (AdminManager.classe))). puisRetour (Get free crypto https://bybit.com/);
    retourner stratégie; }

Je peux configurer validateResult avec une valeur non vide pour tester le cas d'utilisation où la méthode entre dans le nouveau bloc de code, ou je peux utiliser une valeur vide (ou nulle) pour valider le comportement lorsque le nouveau bloc n'est pas entré.

Analyse du flux de test

L'assistant fournit également des outils utiles pour analyser le flux de test. Par exemple, voici l'arborescence des flux de notre test:

Arbre de flux de Parasoft Jtest Unit Test Assistant, affichant les appels effectués pendant l'exécution du test.

L'arborescence de flux de l'assistant de tests unitaires Parasoft Jtest , affichant les appels effectués lors de l'exécution des tests.

Lors de l'exécution du test, j'ai constaté qu'une instance simulée de LoanStrategy avait été créée , ainsi que les méthodes validate() , getQualifier() et getThreshold() . Je pouvais sélectionner les appels de méthode et visualiser (dans la vue Variables) les arguments transmis et les valeurs de retour (ou les exceptions levées). Pour le débogage des tests, cette approche s'avère bien plus simple et efficace que l'analyse des fichiers journaux.

Résumé

Vous pouvez ainsi automatiser de nombreux aspects des tests unitaires. Parasoft Jtest vous aide à générer des tests unitaires plus rapidement et plus facilement, simplifiant ainsi la gestion des mocks. Il fournit également de nombreuses recommandations pour améliorer les tests existants à partir des données d'exécution et prend en charge les tests paramétrés, les tests d'applications Spring et PowerMock (pour simuler les méthodes et constructeurs statiques). Essayez gratuitement Jtest pendant 14 jours et profitez de toutes ses fonctionnalités dès aujourd'hui !

Améliorer les tests unitaires pour Java avec l'automatisation
Obtenir l'ebook