Site icon Blog ARC Optimizer

Test unitaire d'applications héritées avec JustMock


JustMock facilite la création de tests qui isolent nos modifications de code, même dans les applications héritées « non testables ».

Les articles qui démontrent des tests automatisés supposent généralement que vous travaillerez avec des applications qui reflètent les pratiques de codage actuelles, y compris les pratiques souvent adopté spécifiquement pour rendre le code « testable ». Étant donné que nous passons tous la plupart de notre temps à étendre, améliorer et (occasionnellement) réparer des applications « héritées », ce n'est pas le code avec lequel nous travaillons généralement.

En fait, le code avec lequel nous travaillons est souvent considéré comme « non testable »— il appelle des membres statiques ou des méthodes d'extension, a des méthodes qui acceptent des classes plutôt que des interfaces, dépend des champs déclarés au niveau de la classe, interagit avec des objets Framework ou fonctionne avec des données extraites d'une base de données à l'aide d'Entity Framework. Ces pratiques rendent difficile la création de tests qui échouent uniquement à cause des modifications que nous apportons, plutôt qu'à cause de certaines modifications de l'environnement du code.

Heureusement, avec JustMock c'est, en fait, facile pour créer une unité automatisée qui fait exactement cela, même avec des applications héritées. :

CustomerContext CustEntities = new CustomerContext();

public decimal CalcShipping(Product prod, int qty, ShippingUrgency urg, DateHeure ShipDate)
{
   
 Client client = (de c dans CustEntities.Clients
       c.CustomerId == prod.CustId
      select c).FirstOrDefault();



decimal urgenceCoût = prod.UrgencyCost(urg);
decimal prodCost = prod.ProdCust( );



   décimal Frais d'expédition = (urgencyCost
        +Stratégie d'expédition.DestCost(cust.Adresses.First(a => a.AddressType == "S"))
        + ShippingStrategy.[19659022]TimeCost(ShipDate, DateTime.Now)
        + prodCost) * qty;

 

  return ShippingCost;
}

Certaines des façons dont un test peut mal tourner sans que ce soit la faute de nos modifications sont faciles à voir. L'instruction LINQ, par exemple, utilise un objet Entity Framework DbContext appelé CustEntities (déclaré comme un champ en haut de la classe) qui extrait les données d'une base de données. Vous pouvez faire fonctionner un test et le voir échouer soudainement, non pas à cause d'une erreur dans votre code, mais parce que quelqu'un a modifié la base de données.

Mais il y a plus. L'une des méthodes appelées à partir de ShippingStrategy est passée à DateTime.Now. Cela crée un exemple classique de la façon dont les tests peuvent mal tourner : si le test est exécuté le lundi et fonctionne, il n'y a aucune garantie que le test fonctionnera le mardi car ce paramètre DateTime.Now renverra une nouvelle valeur.

Les méthodes La classe ShippingStrategy présente un problème différent : nous ne savons pas comment fonctionne le code de ces méthodes. Ce code peut également dépendre de la date ou extraire des données d'une base de données que nous ne connaissons pas. Notre test sera plus fiable si nous pouvons prendre le contrôle de ces méthodes. Cependant, ces deux méthodes sont statiques, ce qui rend plus difficile leur remplacement (en fait, toute la classe ShippingStrategy est marquée comme statique).

Même votre contrôle sur ce qui est transmis à la méthode de l'enfer est limité par le nature de l'objet Produit que la méthode de l'enfer accepte. Le produit est une classe scellée et n'a pas d'interface. Il est hors de question de créer une version fictive de cette classe dont vous pouvez contrôler les propriétés et les méthodes que vous pouvez remplacer.

Oh, et au fait : cette méthode UrgencyCost appelée à partir de l'objet Product — c'est une méthode d'extension.

Il peut sembler qu'il soit impossible de créer un test fiable/répétable pour cette méthode. Mais, avec JustMock, vous pouvez gérer tous ces cas.

Un peu d'entretien est nécessaire avant de pouvoir commencer. Vous devez ajouter un projet de test à votre solution à l'aide du modèle de projet de test C# JustMock installé avec JustMock. (Vous renommerez probablement aussi à la fois la classe par défaut et la méthode par défaut à l'intérieur de cette classe.) Ensuite, avant de commencer à écrire vos tests, vous devez activer le profileur JustMock à partir des extensions de Visual Studio | Menu JustMock.

Vous êtes maintenant prêt à créer un test automatisé autour de la méthode de l'enfer.

Mocking the Product Class Methods (Built-in or Extension)

La première étape de l'écriture d'un test pour cette méthode consiste à créer un objet Product fictif à lui transmettre. Normalement, si vous avez besoin d'un objet fictif pour remplacer un objet réel, vous allez créer un nouvel objet qui implémente l'interface de l'objet réel. Si l'objet n'a pas d'interface, vous pouvez toujours hériter de la classe de l'objet et créer un nouvel objet avec le comportement que vous souhaitez. Bien sûr, dans l'un ou l'autre de ces cas, vous devrez soit modifier le code que vous testez pour qu'il accepte une interface au lieu d'une classe, soit modifier la classe dont vous héritez en marquant les méthodes dans la classe qui vous souhaitez remplacer en tant que virtuel.

Avec JustMock, vous n'avez aucune de ces choses à faire.

La première étape avec JustMock pour prendre le contrôle de la classe Product consiste à en créer une instance. Ensuite, vous passez une expression lambda à la méthode Arrange de la classe Mock de JustMock, en référençant la méthode sur votre objet Product nouvellement créé que vous souhaitez remplacer. Enfin, vous ajoutez la méthode Returns de JustMock pour spécifier la valeur que vous souhaitez renvoyer à partir de la version remplacée de la méthode.

Cet exemple se moque de la méthode ProdCost de l'objet Product pour que la méthode renvoie 1 chaque fois qu'elle est appelée :

Product prod = nouveau Produit();
Mock.Disposer(() => prod.ProdCost()[19659010]).Returns(1);

Il reste cette méthode d'extension UrgencyCost appelée à partir de l'objet Product. Heureusement, JustMock ne se soucie pas vraiment du fait qu'UrgencyCost soit une méthode d'extension – le code moqueur est le même qu'avec une méthode intégrée. Ainsi, cet exemple va encore plus loin et spécifie qu'UrgencyCost doit renvoyer 2 lorsqu'il est appelé, mais uniquement si la méthode est passée ShippingUrgency.High :

Mock.Arrange( () => prod.UrgenceCoût(ShippingUrgency.Élevé))[19659010].Returns(2);

L'objet Product est maintenant configuré pour votre test et est prêt à être transmis à la méthode de l'enfer. Cependant, nous devons toujours prendre le contrôle du code à l'intérieur de la méthode depuis l'enfer. Avant de pouvoir utiliser l'arrangement de la classe Mock avec une classe statique, vous devez configurer la classe statique à l'aide de la méthode SetupStatic de Mock. Pour ce faire, vous passez SetupStatic l'objet Type pour la classe statique, ainsi qu'un paramètre facultatif indiquant la quantité de classe que vous souhaitez simuler (l'exemple suivant utilise StaticConstructor.Mocked pour rendre toutes les méthodes « moquables » parce que, eh bien, pourquoi pas ?).

Après cela, il suffit, encore une fois, de transmettre une expression lambda à la méthode Arrange référençant la combinaison méthode/paramètres que vous souhaitez simuler. Cet exemple va un peu plus loin et, plutôt que de spécifier une valeur de paramètre particulière, utilise l'un des Matchers de JustMock. Les matchers vous offrent une flexibilité supplémentaire pour spécifier les paramètres transmis à une méthode simulée afin que vous n'ayez pas à coder en dur des valeurs spécifiques.

Dans ce cas, le matcher Arg est utilisé pour que les appels à la méthode DestCost renvoient 3 lorsqu'un L'objet d'adresse est passé à la méthode :

Mock.SetupStatic(typeof(ShippingStrategy), StaticConstructor.Moqué);
Mock.Arrange(() => ShippingStrategy.DestCost(Arg.EstTout<Adresse>())).Retours( 3);

Si vous avez lu jusqu'ici, vous pouvez probablement deviner comment vous moquer des objets Framework intégrés, comme DateTime.Now. Le code suivant a la propriété Now sur la classe DateTime qui renvoie toujours le 31 mai 1953. Même si Now est une propriété (plutôt qu'une méthode comme nous l'avons utilisé jusqu'à présent), cela ne fait vraiment aucune différence pour le code JustMock :

Mock.Organiser(() => DateHeure.Maintenant)[19659010].Retours(nouveau DateHeure.Parse("1953/05/31"))[19659010];

Mocking Entity Framework

Enfin, il est temps de traiter l'appel LINQ utilisé avec l'objet CustomerContext DbContext créé dans la méthode de l'enfer. Un travail supplémentaire est nécessaire car vous devez fournir une collection de données de test pour remplacer les données qu'Entity Framework extrairait normalement de la base de données.

Une méthode pour créer et renvoyer une collection appelée dummyCustomers, contenant le client unique à utiliser dans le test (avec un objet Adresse associé), ressemble à ceci :

private IList<Customer> GetDummyCustomers( )
{
   Adresse dummyAddress = new Address { CustomerId = 10, AddressType = "S"[19659010],
        Ville = "Regina", Pays = "Canada" };

   List<Address> dummyAddresss = new List<Address> { dummyAddress };

   Customer dummyCustomer = new Customer { CustomerId = 1,
        Nom complet = "Jason van de Velde",
        Adresses = Adresses factices };

   List<Customer> dummyCustomers = new List<Customer> { dummyCustomer };
}

Vous n'avez pas besoin d'encapsuler ce code dans une méthode, mais des collections comme celle-ci sont souvent utiles dans plusieurs tests. Envelopper le code dans une méthode facilite la réutilisation de la collection (et, de plus, la partie arranger de ce test est déjà assez encombrée avec tous les autres codes moqueurs requis par la méthode de l'enfer).

Il ne reste plus qu'à instancier l'objet DbContext que ShippingCostCalculator utilise et en exploitant la méthode Arrange. Dans ce cas, nous voulons que tout appel à la propriété Customers sur CustomerContext, même sur l'objet CustomerContext utilisé dans la méthode de l'enfer, renvoie cette collection d'objets Customer factices.

Cela nécessite l'utilisation de deux nouvelles méthodes avec Arrange : IgnoreInstance et ReturnsCollection. La méthode ReturnsCollection entraîne l'utilisation de la méthode GetDummyCustomer pour fournir ces données ; IgnoreInstace garantit que cette simulation est appliquée à l'objet CustomerContext utilisé dans la méthode de l'enfer et pas seulement au CustomerContext dans le code de test.

Le code de moquerie DbContext dans le test finit par ressembler à ceci :

CustomerContext mockedEntities = nouveau CustomerContext();
Mock.Disposer(() => mockedEntities.Clients)
        .[19659022]IgnorerInstance()
        .ReturnsCollection(GetDummyCustomers());[1965902]Si nous faisons tout de ces changements, nous aurons isolé notre test automatisé des changements dans l'environnement (et de nombreux tests peuvent ne pas nécessiter tous ces changements). Maintenant, nous pouvons améliorer la méthode de l'enfer, exécuter nos tests et savoir que si le test échoue, c'est à cause de nos modifications.

Voici à quoi ressemble la version finale du test :

[ Méthode de test]
public void CalcShippingTest()
{
   
   ShippingCostCalculator scc = nouveau ShippingCostCalculator();
   DateHeure Date d'expédition = DateHeure.Parse("2021/01/01");
         
   
   Produit produit = nouveau Produit();
   prod.CustId = 1;
   Mock.Disposer(() => prod.ProdCost()[19659010]).Retours(1);
   Mock.Organiser(() => prod.UrgenceCoût(ShippingUrgency.Élevé)).Retours(2);

   
   Mock.SetupStatic(typeof(ShippingStrategy), StaticConstructor.Mocked)[19659010];
   Mock.Arrange(() => ShippingStrategy.DestCost(Arg.EstAny<Adresse>())).Retours( 3);

   
   Mock.Organiser(() => DateHeure.Maintenant).[19659022]Retours(DateHeure.Parse("1953/05/31"));

   
   CustomerContext mockedEntities = new CustomerContext();
   Mock.Disposer(() => mockedEntities.Clients)
                .[19659022]IgnorerInstance()
                .ReturnsCollection(GetDummyCustomers());

   
   décimal res = scc.CalcShipping(prod, 200, ShippingUrgency.High, ShipDate);

   Assert.AreEqual(1200, res);
}

Espérons que vos anciennes méthodes ne se présenteront pas tout à fait autant de défis que cette méthode fait. Mais avec ces outils, vous pouvez automatiser les tests à la fois pour votre code hérité et pour tout nouveau code brillant et brillant que vous obtenez. En supposant que vous obteniez un nouveau code brillant et brillant, bien sûr.




Source link
Quitter la version mobile