• Accueil / Salesforce / Développement piloté par…
, Développement piloté par les tests, mocking et Force DI<span class="wtr-time-wrap after-title"><span class="wtr-time-number">7</span> minutes de lecture</span>

Développement piloté par les tests, mocking et Force DI7 minutes de lecture


Dans ce blog, je souhaite souligner l’utilisation de Test.createStub avec Force DI, pour injecter efficacement des implémentations simulées de classes dépendantes. Nous allons passer par un petit exemple montrant un classe de premier niveau avec deux dépendances. Afin de tester cette classe isolément dans le cadre d’un vrai test unitaire, nous allons nous moquer de ses dépendances puis injecter les mocks. Ce blog montrera également comment Force DI ajoute de la valeur en extraction et encapsulation du code de configuration depuis l’application.

Exigences de ChatApp

le message d’accueil pour l’application doit être déterminée par un paramètre d’absence du bureau combiné à un message initial configurable. Les développeurs décident de diviser les préoccupations liées à la satisfaction de cette exigence en deux classes. Une instance du Afficher classe gérera la partie système du message et une instance du Message classe sera utilisé pour obtenir le reste. Principes de programmation orientés objet (POO) ont été utilisés pour créer un interface pour l’affichage et classe de base pour Message. Les implémentations de ceux-ci ne sont pas d’un grand intérêt ici et ne sont donc pas montrées.

, Développement piloté par les tests, mocking et Force DI<span class="wtr-time-wrap after-title"><span class="wtr-time-number">7</span> minutes de lecture</span>

La vie sans injection de dépendance

Ce qui suit montre d’abord comment le ChatApp regards sans injection de dépendance.

    public class ChatApp {

        private Display display;
        private Message welcomeMessage;

        public ChatApp() {

            // Display and Message impls vary based on out of office
            if (UserAvailability__c.getInstance().OutOfOffice__c) {
               display = new Fun();
               welcomeMessage = new Weekend();
            } else {
               display = new BeAwesome();
               welcomeMessage = new Weekday();
            }
        }

        public String greetings() {

            // Custom message to greet the user is prefix by the display sentiment
            return display.startup() + ':' + welcomeMessage.saySomething();
        }
    }

En lisant le code de test Apex suivant, tenez compte des éléments suivants: –

  • Il teste uniquement directement le ChatApp.greetings méthode mais aucune de ses dépendances.
  • Pour mettre en place le test, les développeurs devaient décider de l’approche de configuration (paramètre personnalisé).
  • Aditionellement, implémentations d’affichage et de message devait également être codé (non illustré).
  • Les développeurs devaient faire pas mal de travail pour arriver au point où ils pourraient écris le premier test.
    @IsTest
    private static void givenOutOfOfficeOnWeekDayWhenGreetingThenGreatWeekend() {

        // Given
        UserAvailability__c config = new UserAvailability__c();
        config.OutOfOffice__c = true;
        insert config;

        // When
        ChatApp chatApp = new ChatApp();
        String greeting = chatApp.greetings();

        // Then
        System.assertEquals('Party time!:Have a great weelend!', greeting);
    }

Maintenant, imaginez aussi que le Classe d’affichage a d’autres dépendances et exigences de configuration, qui ajouteraient au code de configuration de test. Il n’est pas non plus très favorable à Développement piloté par les tests car ChatApp nécessitait l’implémentation d’autres dépendances pour que le test s’exécute. Techniquement, cela ressemble à un Test d’intégration, plus qu’un test unitaire.

La vie avec l’injection de dépendance

Ce qui suit montre comment le ChatApp regards avec injection de dépendance.

    public class ChatApp {

        private Display display =
           (Display) di_Injector.Org.getInstance(Display.class);
        private Message welcomeMessage =
           (Message) di_Injector.Org.getInstance(Message.class);        

        public String greetings() {

            // Custom message to greet the user is prefix by the display sentiment
            return display.startup() + ':' + welcomeMessage.saySomething();
        }
    }

REMARQUE: Il est intentionnel qu’il n’y ait aucune référence à des implémentations d’affichage et de message

En lisant le code de test Apex suivant, tenez compte des éléments suivants: –

  • Il seulement teste le ChatApp.greetings code de méthode et rien d’autre.
  • Décisions relatives au fonctionnement de la configuration où pas nécessaire.
  • Implémentations d’affichage et de message n’étaient pas nécessaires.
  • Aussi tôt que le salutations méthode a été codé un le test pourrait être écrit.

    /**
     * Unit test for the ChatApp.greetings method with mocked dependencies
     **/
    @IsTest
    private static void givenDisplayAndMessageWhenAppGreatingThenCombinedMessage() {

        // Given
        ChatAppMockProvider mockProvider = new ChatAppMockProvider();
        di_Injector.Org.Bindings.set(new di_Module()
          .bind(Message.class).toObject(Test.createStub(Message.class, mockProvider))
          .bind(Display.class).toObject(Test.createStub(Display.class, mockProvider)));

        // When
        ChatApp app = new ChatApp();
        String message = app.greetings();

        // Then
        System.assertEquals('Mock Startup Message:Mock Message', message);
    }

    /**
     * Basic Stub Provider for ChatApp dependencies
     **/
    private class ChatAppMockProvider implements System.StubProvider {
        public Object handleMethodCall(Object stubbedObject, String stubbedMethodName, Type returnType, List listOfParamTypes, List listOfParamNames, List listOfArgs) {
            if(stubbedMethodName == 'saySomething') {
                return 'Mock Message';
            } else if(stubbedMethodName == 'startup') {
                return 'Mock Startup Message';
            }
            return null;
        }
    }

En utilisant la Force DI di_Injector.Org.Bindings.set méthode et Test.createStub (en savoir plus ici) Les implémentations simulées de méthode sont injectées à la place d’implémentations concrètes (elles n’ont même pas besoin d’exister à ce stade). Cela évite d’avoir à écrire plus de code que nécessaire pour arriver à un point où un test peut être écrit et exécuté. L’utilisation de ces deux technologies rapproche l’expérience développeur de celle de Développement piloté par les tests.

Qu’en est-il des tests d’intégration?

Comme je l’ai mentionné plus tôt, il n’y avait pas nécessairement de problème avec le code de test initial, c’est juste qu’en termes de test unitaire et de TDD, sa portée était trop large (il a testé plus que nécessaire). Donc, si nous voulions exécuter ce test original maintenant que nous avons implémenté DI au-dessus de quoi d’autre est-il nécessaire?

La question que vous vous posez probablement est la suivante: comment les implémentations réelles (non simulées) de Display et Message sont exécutées? C’est là qu’intervient une autre fonctionnalité de Force DI. Modules Force DI peut être créé dynamiquement par le code de test unitaire ci-dessus et / ou configuré via les métadonnées personnalisées de liaison. Une fois les métadonnées personnalisées de liaison configurées, le code suivant s’exécute automatiquement dans le cadre du Initialisation Injector.Org. D’autres liaisons définies par le code pour DI utilisées ailleurs dans l’application iraient également ici (en savoir plus).

, Développement piloté par les tests, mocking et Force DI<span class="wtr-time-wrap after-title"><span class="wtr-time-number">7</span> minutes de lecture</span>

    public class ChatAppConfiguration extends di_Module {
        /**
         * Binding configuration for the ChatApp
         **/
        public override void configure() {            

            // Display and Message impls vary based on out of office
            if (UserAvailability__c.getInstance().OutOfOffice__c) {
                bind(Message.class).to(Weekend.class);
                bind(Display.class).to(Fun.class);
            } else {
                bind(Message.class).to(Weekday.class);
                bind(Display.class).to(BeAwesome.class);
            }
        }
    }

Vous trouverez ci-dessous le code de test d’origine avec lequel nous avons commencé, mais cette fois, le code ChatApp a utilisé la classe Injector pour résoudre ses dépendances. Étant donné qu’en production, ces liaisons ne sont pas remplacées via Injector.Org.Bindings.set méthode. Le code de l’injecteur utilise la configuration de liaison basée sur les métadonnées personnalisées, qui appelle la logique du module ci-dessus.

    private static void givenOutOfOfficeOnWeekDayWhenGreetingThenGreatWeekend() {

        // Given
        UserAvailability__c config = new UserAvailability__c();
        config.OutOfOffice__c = true;
        insert config;

        // When
        ChatApp chatApp = new ChatApp();
        String greeting = chatApp.greetings();

        // Then
        System.assertEquals('Party time!:Have a great weelend!', greeting);
    }

Sommaire

Il convient de noter que si vous souhaitez inclure une configuration requise dans le cas d’utilisation de ChatApp, les exemples ci-dessus peuvent sembler trop complexes pour les besoins de base. Il peut également sembler un peu intimidant d’utiliser des concepts de POO comme les interfaces et les classes de base pour les nouveaux développeurs. Eh bien, bonne nouvelle! Il s’avère que vous pouvez également utiliser cette approche avec des classes autonomes régulières. Jetez un œil au cas d’utilisation plus simple ci-dessous.

    public class ChatApp {

        // Inject WelcomeMessage
        private WelcomeMessage message =
             (WelcomeMessage) di_Injector.Org.getInstance(WelcomeMessage.class);

        // ...
    }

    public class ChatAppConfiguration extends di_Module {

        public override void configure() {            

            // Configure binding
            bind(WelcomeMessage.class).to(WelcomeMessage.class);
        }
    }

    @IsTest
    private static void givenDisplayAndMessageWhenAppGreatingThenCombinedMessage() {

        // Inject mock WelcomeMessage
        di_Injector.Org.Bindings.set(new di_Module()
          .bind(WelcomeMessage.class).toObject(
             Test.createStub(WelcomeMessage.class, mockWelcomeMessage)));

        // ...
    }

REMARQUE: Comme mentionné dans le blog précédent, Force DI est basé à peu près sur Guice Java, qui a également un excellent exemple travaillé ici. Les principes expliqués sont les mêmes qu’ici, cependant, les exemples utilisent des annotations pour permettre à l’injecteur du Guide Java de détecter automatiquement où faire l’injection. Dans Force DI, cela doit être exprimé directement via Injector.Org.getInstance.



Source de l’article traduit automatiquement en Français

Besoin d'aide ?
Voulez-vous utiliser Pardot à sa capacité maximale et avoir
+ DE LEADS QUALIFIÉS

Notre analyse de votre Pardot offerte dès aujourd'hui
Merci, vous pouvez compléter notre questionnaire
Nous allons revenir vers vous rapidement !

Fermer