12
votes

Test de l'unité, moqueur - cas simple: service - référentiel

Considérons une pièce de service suivante: xxx pré>

Prenons un test d'unité simple: P>

public Product GetProduct(int id) {
   try {
      var product = _productRepository.GetProduct(id);

      product.Owner = "totallyDifferentOwner";

      return product;
   } catch (Exception e) {
      // log, wrap then throw
   }
}


4 commentaires

Pouvez-vous élaborer l'indice 2 - Pourquoi ne peut pas faire partie de la solution?


@MDMA: COZ


Bien sûr, j'impliquais que vous utilisiez différents objets. Comme vous le verrez dans ma réponse - en utilisant la même référence n'est pas une stratégie de test efficace.


Astuce n ° 4 n'est pas correcte. Comme vous l'avez déjà noté, il est facile de prouver que le Sut renvoie le même objet car pour les types de référence .net "Le même objet" signifie "le même emplacement en mémoire". En plus de prouver que la méthode de service renvoie le même objet, vous souhaitez prouver que il n'a pas modifié cet objet .


12 Réponses :


2
votes

Q1: Ne modifiez pas le code puis écrivez un test. Écrivez d'abord un test pour le comportement attendu. Ensuite, vous pouvez faire ce que vous voulez à la SUT.

Q2: Vous ne apportez pas les modifications dans votre porte code> passerelle pour modifier le propriétaire du produit. Vous faites le changement dans votre modèle. P>

Mais si vous insistez, écoutez vos tests. Ils vous disent que vous avez la possibilité que des produits soient tirés de la passerelle qui possèdent les propriétaires incorrects. Oups, ressemble à une règle d'affaires. Devrait être testé pour dans le modèle. P>

Aussi votre utilisation d'un simulacre. Pourquoi testez-vous un détail de mise en œuvre? La passerelle ne se soucie que du _productrepository.getproduct (ID) code> renvoie un produit. Pas ce que le produit est. P>

Si vous testez de cette manière, vous créerez des tests fragiles. Et si le produit change plus loin. Maintenant, vous avez échoué des tests partout. P>

Vos consommateurs de produits (modèle) sont les seuls à se soucier de la mise en œuvre du produit code>. P>

Donc, votre test de passerelle devrait ressembler à ceci: P>

[Test]
public void GetProduct_return_the_same_product_as_getProduct_on_productRepository() {
   var product = EntityGenerator.Product();

   _productRepositoryMock.Setup(pr => pr.GetProduct(product.Id)).Returns(product);

   _productService.GetProduct(product.Id);

   _productRepositoryMock.VerifyAll();
}


11 commentaires

Vous vous trompez .. La passerelle se soucie de ce que l'IdProctrepository.getProduct (ID) renvoie un produit et cette passerelle le retourne également non modifié. Je dois tester qu'un produit renvoyé par passerelle est exactement le même que celui récupéré par un référentiel.


Bon! Une différence d'opinion. IMHO, seuls les consommateurs de produit se soucie de savoir s'il s'agit d'un produit valide . La passerelle n'est censée que si un produit est envoyé dans un référentiel et qu'un produit est renvoyé d'un référentiel. Validation du produit se produit dans le modèle. Je ne dis pas que tu ne peux pas le faire. Bien sûr, vous pouvez effectuer la validation de produits dans votre passerelle. Mais je crois que vous mélangez des inquiétudes et que vous mettez trop de responsabilité sur la passerelle.


@rafek, si un produit a été inséré de manière incorrecte (gigo), où valideriez-vous cela?


Et encore - j'aimerais tester que le service ne modifiera pas le résultat de l'appel du référentiel. Le produit pourrait être modifié ici mais être toujours valable dans le sens de la validation des entreprises. Il ne s'agit pas d'insertion - il s'agit de récupération.


Ok, essayez de ne pas avoir de setters sur votre produit . Utilisez uniquement des interfaces conçues pour le comportement.


@Gutzofter: Est-ce que cela changera quelque chose?


Oui je pense que ça fait. Ce que vous inquiétez sur c'est qu'un objet mis dans votre référentiel est l'objet réel qui y a été mis. Mon point était que tous les tests pour cela devraient être effectués dans l'objet qui utilise réellement produit . Si vous n'avez pas de configurissures, cela ne peut jamais arriver: produit.OWNER = "TOTALYLYLELYDIFFERERTOWNER";


@rafek, je reçois votre point d'hint1. Ma faute! Lorsque vous faites le comparateur dans l'ASSTER, les deux produits point sur la même référence. Ils sont le même objet.


@Gutzofter: Ma solution est que je clonage du produit (Nom It: attenduproduct), puis comparez-le à la rentrée .. Mais je me demandais comment d'autres gèrent ce "problème". :)


@rafek - pourquoi ne pas essayer un verrou. Quelque chose comme un verrou de fichier. Créer un TestProduction immuable Il héritera du produit . Le TestProduct lancera une exception lorsque les propriétés sont modifiées. Ou vous pouvez créer un Strict Mock Mock produit et utiliser cela. Il ne semble pas vraiment y avoir d'autre manière que ces trois, y compris votre suggestion.


Ré-éditez votre question et mettez comment vous le faites actuellement. Il apparaîtra sur la liste récente avec la modification. Peut-être que vous aurez plus de réponses.



10
votes

Personnellement, je me soucierais de cela. Le test devrait s'assurer que le code fait ce que vous avez l'intention. Il est très difficile de tester quel code est pas em> faire strong>, je ne me dérangerais pas dans ce cas.

Le test devrait simplement ressembler à ceci: p>

[Test]
public void GetProduct_GetsProductFromRepository() 
{
   var product = EntityGenerator.Product();

   _productRepositoryMock
     .Setup(pr => pr.GetProduct(product.Id))
     .Returns(product);

   Product returnedProduct = _productService.GetProduct(product.Id);

   Assert.AreSame(product, returnedProduct);
}


2 commentaires

Partout où je cherche des exemples pour obtenir une liste de quelque chose ou obtenir quelque chose (célibataire). Où puis-je trouver un exemple pour créer / mettre à jour quelque chose.Je veux éviter les opérations de DB.Il y a-t-il un moyen de le faire?


@prashanth: Je n'essaierais pas de simuler la base de données. Voir ceci: Stackoverflow.com/a/804536/27343



0
votes

Eh bien, une manière est de transmettre une maquette de produit plutôt que du produit réel. Vérifier rien pour affecter le produit en le rendant strict. (Je suppose que vous utilisez MOQ, on dirait que vous êtes)

[Test]
public void GetProduct_return_the_same_product_as_getProduct_on_productRepository() {
   var product = new Mock<EntityGenerator.Product>(MockBehavior.Strict);

   _productRepositoryMock.Setup(pr => pr.GetProduct(product.Id)).Returns(product);

   Product returnedProduct = _productService.GetProduct(product.Id);

   Assert.AreEqual(product, returnedProduct);

   _productRepositoryMock.VerifyAll();
   product.VerifyAll();
}


0 commentaires

0
votes

Je ne suis pas sûr, si le test de l'unité devrait se soucier de "quelle méthode donnée est pas ". Il y a des zillions étapes qui sont possibles. Dans Strict the Test "GETPRODUCT (ID) renvoie le même produit que GETPRODUCT (ID) sur ProductTrepository" est correct avec ou sans la ligne produit.OWNER = "TOTALYLYDIFFERENTEURTOWNER" . .

Cependant, vous pouvez créer un test (si nécessaire) "GETPRODUCT (ID) Retourner le produit avec le même contenu que GETPRODUCT (ID) sur ProductrePository" dans lequel vous pouvez créer un clone (proportionnellement profond) d'une instance de produit, puis vous devriez alors Comparez les contenus des deux objets (donc pas d'objet.equals ou d'objet.reeferequens).

Les tests de l'unité ne garantissent pas 100% de comportement sans bogue et de comportement correct.


0 commentaires

3
votes

Un moyen de penser à des tests unitaires est en tant que spécifications codées. Lorsque vous utilisez l'entité entitygenerator pour produire des instances à la fois pour le test et pour le service actuel, on peut constater votre test pour exprimer l'exigence

  • Le service utilise l'entitéGenerator pour produire des instances de produit.

    C'est ce que votre test vérifie. C'est sous-spécifié car il ne mentionne pas si des modifications sont autorisées ou non. Si nous disons

    • Le service utilise l'entitéGenerator pour produire des instances de produit, qui ne peuvent pas être modifiées.

      Ensuite, nous obtenons un indice quant aux modifications de test nécessaires pour capturer l'erreur: xxx

      (Le changement est que nous récupérons le propriétaire de la fraîcheur Produit créé et vérifiez que le propriétaire du produit est renvoyé à partir du service.)

      Ceci incarne le fait que le propriétaire et les autres propriétés du produit doivent être égaux à la valeur d'origine du générateur. Cela peut sembler être l'évidence, car le code est assez trivial, mais cela fonctionne assez profond si vous pensez en termes de spécifications des exigences.

      J'ai souvent "tester mes tests" en stipulant "si Je change cette ligne de code, modifier une constante essentielle ou deux ou injecter quelques fleurs de code (par exemple, la modification! = TO ==), quel test va capturer l'erreur? " Le faire pour de vraies trouvailles s'il y a un test qui capture le problème. Parfois, dans ce cas, il est temps de regarder les exigences implicites dans les tests et voyez comment nous pouvons les resserrer. Dans des projets sans réel Capture / analyse Ceci peut être un outil utile pour renforcer les tests afin qu'ils échouent lorsque des changements inattendus se produisent.

      Bien sûr, vous devez être pragmatique. Vous ne pouvez pas raisonnablement vous attendre à gérer tous les changements - certains seront simplement absurdes et le programme va se planter. Mais les changements logiques tels que le changement de propriétaire sont de bons candidats pour le renforcement des tests.

      En faisant glisser la discussion des exigences en une solution de codage simple, certains peuvent penser que je suis parti de l'extrémité profonde, mais des exigences approfondies aident à produire en profondeur tests, et si vous n'avez aucune exigence, vous devez travailler doublement pour vous assurer que vos tests sont approfondis, car vous effectuez implicitement la capture des exigences lorsque vous écrivez les tests.

      EDIT: je suis répondre à cela de l'intérieur des contraintes définies dans la question. Compte tenu d'un choix libre, je suggérerais d'utiliser l'entitéGenerator pour créer des instances de test de produit et les créera plutôt "à la main" et utiliser une comparaison sur l'égalité. Ou plus directe, comparez les champs du produit retourné à des valeurs spécifiques (codées rigides) dans le test, à nouveau, sans utiliser l'entitéGenerator dans le test.


0 commentaires

3
votes

Pourquoi ne vous moquez-vous pas le produit ainsi que le productrepository ?

Si vous vous moquez du produit à l'aide d'un strict maquette, vous obtiendrez une panne lorsque le référentiel touche votre produit.

S'il s'agit d'une idée complètement ridicule, pouvez-vous s'il vous plaît expliquer pourquoi? Honnêtement, j'aimerais apprendre.


0 commentaires

0
votes

Vous pouvez renvoyer une interface au produit au lieu d'un produit en béton.

tel que p> xxx pré>

puis vérifier la propriété propriétaire n'a pas été défini: p>

[Specification]
public class When_product_service_has_get_product_called_with_any_id 
       : ProductServiceSpecification
{
   private int _productId;

   private IProduct _actualProduct;

   [It] 
   public void Should_return_the_expected_product()
   {
     this._actualProduct.Should().Be.EqualTo(Dep<IProduct>());
   }

   [It]
   public void Should_not_have_the_product_modified()
   {
     Dep<IProduct>().AssertWasNotCalled(p => p.Owner = Arg<string>.Is.Anything);

     // or write your own extension method:
     // Dep<IProduct>().AssertNoPropertyOrMethodWasCalled();
   }


   public override void GivenThat()
   {
     var randomGenerator = new RandomGenerator();
     this._productId = randomGenerator.Generate<int>();

     Stub<IProductRepository, IProduct>(r => r.GetProduct(this._productId));
   }

   public override void WhenIRun()
   {
       this._actualProduct = Sut.GetProduct(this._productId);
   }
}


1 commentaires

Introduisez l'interface à mon entité de domaine? Seulement pour les tests? Pas très élégant, je pense.



1
votes

Si vous souhaitez vraiment vous garantir que la méthode de service ne modifie pas les attributs de vos produits, vous avez deux options:

  • Définissez les attributs de produit attendus dans votre test et affirmez que le produit résultant correspond à ces valeurs. (Cela semble être ce que vous faites maintenant en clonant l'objet.) P> li>

  • Mock le produit strong> et spécifiez les attentes pour vérifier que la méthode de service ne modifie pas ses attributs. P> li> ul>

    C'est comme ça que je ferais ce dernier avec Nmock: P>

    // If you're not a purist, go ahead and verify all the attributes in a single
    // test - Get_Product_Does_Not_Modify_The_Product_Returned_By_The_Repository
    [Test]
    public Get_Product_Does_Not_Modify_Owner() {
    
        Product mockProduct = mockery.NewMock<Product>(MockStyle.Transparent);
    
        Stub.On(_productRepositoryMock)
            .Method("GetProduct")
            .Will(Return.Value(mockProduct);
    
        Expect.Never
              .On(mockProduct)
              .SetProperty("Owner");
    
        _productService.GetProduct(0);
    
        mockery.VerifyAllExpectationsHaveBeenMet();
    }
    


0 commentaires

1
votes

Mes stands de réponse précédente, bien qu'il suppose que les membres de la classe de produits que vous vous souciez sont publics et virtuels. Ceci n'est pas probable si la classe est un poco / dto.

Ce que vous recherchez peut être reproché comme un moyen de comparer les valeurs (non pas d'instance) de l'objet. P>

un façon de comparer pour voir s'ils correspondent lorsqu'ils sont sérialisés. Je l'ai fait récemment pour certains code ... remplaçait une liste de paramètres longue avec un objet paramétré. Le code est CRUFTY, je ne veux pas le refroidir comme ça comme ça va bientôt de toute façon. Donc, je viens de faire cette comparaison de sérialisation comme un moyen rapide de voir s'ils ont la même valeur. P>

J'ai écrit des fonctions utilitaires ... ASSERT2.issamevalue (attendu, réel) qui fonctionne comme celle de Nunit. ASSERT.AREEQUAL (), sauf que cela serifie via JSON avant de comparer. De même, it2.issamesérialisalisé () peut être utilisé pour décrire les paramètres transmis à des appels moqués de manière similaire à Moq.it.is (). P>

public class Assert2
{
    public static void IsSameValue(object expectedValue, object actualValue) {

        JavaScriptSerializer serializer = new JavaScriptSerializer();

        var expectedJSON = serializer.Serialize(expectedValue);
        var actualJSON = serializer.Serialize(actualValue);

        Assert.AreEqual(expectedJSON, actualJSON);
    }
}

public static class It2
{
    public static T IsSameSerialized<T>(T expectedRecord) {

        JavaScriptSerializer serializer = new JavaScriptSerializer();

        string expectedJSON = serializer.Serialize(expectedRecord);

        return Match<T>.Create(delegate(T actual) {

            string actualJSON = serializer.Serialize(actual);

            return expectedJSON == actualJSON;
        });
    }
}


0 commentaires

0
votes

Si tous les consommateurs de produitsService.getProduct () attendent le même résultat que s'ils l'avaient demandé au producteur, pourquoi ne veulent-ils pas appeler productrexitory.getProduct () lui-même? Il semble que vous ayez un Middle Man ici.

Il n'y a pas beaucoup de valeur ajoutée à Produitservice.getProduct (). Dump it et que les objets client appellent le produit producteur.getProduct () directement () directement. Mettez la gestion des erreurs et de vous connecter à ProductrePository.getProduct () ou du code de consommateur (éventuellement via AOP).

Pas plus de milieu, pas plus de problème de divergence, plus besoin de tester cette divergence.


0 commentaires

0
votes

laissez-moi indiquer le problème comme je le vois.

  1. Vous avez une méthode et une méthode de test. La méthode de test valide la méthode d'origine.
  2. Vous modifiez le système sous test en modifiant les données. Ce que vous voulez voir, c'est que le même test d'unité échoue.

    Donc, en effet, vous créez un test qui vérifie que les données de la source de données correspondent aux données de votre objet récupéré une fois la couche de service le renvoie. Qui tombe probablement sous la classe de "test d'intégration".

    Vous n'avez pas beaucoup d'options de bonnes options dans ce cas. En fin de compte, vous voulez savoir que chaque propriété est la même que de la valeur de la propriété passée. Donc, vous êtes obligé de tester chaque propriété de manière indépendante. Vous pouvez le faire avec une réflexion, mais cela ne fonctionnera pas bien pour les collections imbriquées.

    Je pense que la vraie question est la suivante: pourquoi tester votre modèle de service pour l'exactitude de votre couche de données et pourquoi écrivez le code dans votre modèle de service pour casser le test? Êtes-vous inquiet que vous ou d'autres utilisateurs puisse définir des objets sur des états non valides de votre couche de service? Dans ce cas, vous devriez changer votre contrat afin que le produit.OWNER soit loadonly .

    Vous feriez mieux d'écrire un test sur votre couche de données afin de vous assurer qu'il récupère correctement les données, puis utilisez des tests d'unité pour vérifier la logique commerciale dans votre couche de service. Si vous êtes intéressé à plus de détails sur cette approche, répondre dans les commentaires.


0 commentaires

0
votes

Ayant examiné les 4 astuces à condition qu'il semble que vous souhaitiez faire immuable un objet immuable au moment de l'exécution. La langue C # ne supporte aucun soutien. Il est possible uniquement de refactoriser la classe de produit elle-même. Pour le refactoring, vous pouvez prendre iReadonlyProduct approche et protéger toutes les setters d'être appelés. Ceci permet toutefois toujours la modification d'éléments de conteneurs tels que la liste <> est renvoyé par getters. La collection Readonly ne vous aidera pas non plus. Seul WPF vous permet de modifier l'immuabilité au moment de l'exécution avec Cergezable classe.

Je vois donc le seul moyen approprié de s'assurer que les objets ont le même contenu est en les comparant. Le moyen le plus simple serait probablement d'ajouter [sérialisable] attribuer à toutes les entités impliquées et faire la sérialisation - avec la comparaison, comme suggéré par Frank Schwieterman.


0 commentaires