Vraag Unit testen, spot - eenvoudig geval: Service - Repository


Overweeg een volgend stuk service:

public class ProductService : IProductService {

   private IProductRepository _productRepository;

   // Some initlization stuff

   public Product GetProduct(int id) {
      try {
         return _productRepository.GetProduct(id);
      } catch (Exception e) {
         // log, wrap then throw
      }
   }
}

Laten we een eenvoudige eenheidscontrole overwegen:

[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);

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

   Assert.AreEqual(product, returnedProduct);

   _productRepositoryMock.VerifyAll();
}

In eerste instantie lijkt het erop dat deze test in orde is. Maar laten we onze servicemethode een beetje veranderen:

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

      product.Owner = "totallyDifferentOwner";

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

Hoe een bepaalde test herschrijven die hij zou doorstaan ​​met de eerste servicemethode en mislukken met een tweede?

Hoe ga je met dit soort om eenvoudig scenario's?

TIP 1: Een gegeven test is een slecht coz-product en geretourneerdProduct is eigenlijk dezelfde referentie.

TIP 2: Het implementeren van gelijkheidsleden (object.equals) is niet de oplossing.

HINT 3: Wat nu het geval is, ik maak een kloon van de Product-instantie (expectedProduct) met AutoMapper - maar deze oplossing bevalt mij niet.

TIP 4: Ik test niet dat de SUT NIET sth doet. Ik probeer te testen dat SUT hetzelfde object retourneert als het wordt geretourneerd uit de repository.


12
2018-05-08 06:29


oorsprong


antwoorden:


Persoonlijk zou ik hier niets om geven. De test moet ervoor zorgen dat de code doet wat u van plan bent. Het is erg moeilijk om te testen welke code is niet aan het doenIk zou in dit geval niet storen.

De test zou er eigenlijk zo uit moeten zien:

[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);
}

Ik bedoel, het is een regel code die je aan het testen bent.


9
2018-05-19 06:33



Waarom spot je niet met de product net als de productRepository?

Als je spot met de product gebruik maken van een streng mock, je krijgt een fout wanneer de repository je product raakt.

Als dit een volkomen belachelijk idee is, kunt u alsjeblieft uitleggen waarom? Eerlijk gezegd wil ik het leren.


3
2018-05-19 21:49



Een manier om unit tests te beschouwen is als gecodeerde specificaties. Wanneer u de EntityGenerator om exemplaren te produceren voor de test en voor de feitelijke service, kan uw test worden gezien als uitdrukking van de vereiste

  • De Service gebruikt de EntityGenerator om Productinstanties te produceren.

Dit is wat uw test verifieert. Het is niet gespecificeerd omdat het niet vermeldt of wijzigingen zijn toegestaan ​​of niet. Als we het zeggen

  • De Service gebruikt de EntityGenerator om Productinstanties te produceren, die niet kunnen worden gewijzigd.

Vervolgens krijgen we een hint over de testwijzigingen die nodig zijn om de fout vast te leggen:

var product = EntityGenerator.Product();
// [ Change ] 
var originalOwner = product.Owner;  
// assuming owner is an immutable value object, like String
// [...] - record other properties as well.

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

Assert.AreEqual(product, returnedProduct);

// [ Change ] verify the product is equivalent to the original spec
Assert.AreEqual(originalOwner, returnedProduct.Owner);
// [...] - test other properties as well

(De wijziging is dat we de eigenaar ophalen uit het nieuwe product en de eigenaar controleren op het product dat door de service is geretourneerd.)

Dit belichaamt het feit dat de eigenaar en andere producteigenschappen gelijk moeten zijn aan de oorspronkelijke waarde van de generator. Dit lijkt misschien het meest voor de hand te liggen, aangezien de code vrij triviaal is, maar hij loopt behoorlijk diep als je denkt in termen van vereiste specificaties.

Ik "test mijn testen vaak" door te stellen "als ik deze regel code wijzig, een kritische constante of twee wijzig, of een paar codebuiltjes injecteer (bijv. Wijzigen! = Naar ==), welke test zal de fout dan opvangen?" Doe het voor echte vondsten als er een test is die het probleem oplost. Soms niet, in welk geval het tijd is om naar de impliciete eisen van de tests te kijken en te kijken hoe we ze kunnen opscherpen. In projecten zonder echte vastlegging / analyse kan dit een handig hulpmiddel zijn om tests strenger te maken, zodat ze falen als zich onverwachte veranderingen voordoen.

Natuurlijk moet je pragmatisch zijn. Je kunt redelijkerwijs niet verwachten dat je met alle veranderingen omgaat - sommige zullen gewoonweg absurd zijn en het programma zal crashen. Maar logische veranderingen zoals de verandering van de eigenaar zijn goede kandidaten voor het versterken van de test.

Door slepen van eisen over te brengen naar een eenvoudige codeeroplossing, denken sommigen misschien dat ik van het diepe af ben gegaan, maar grondige vereisten helpen bij grondige tests en als u geen vereisten hebt, moet u dubbel hard werken om ervoor te zorgen dat uw tests grondig zijn, omdat je impliciet eisen vastlegt terwijl je de tests schrijft.

EDIT: Ik beantwoord dit vanuit de tegenstrijdigheden in de vraag. Bij een vrije keuze zou ik willen voorstellen om de EntityGenerator niet te gebruiken om product-testinstanties te maken en ze in plaats daarvan "met de hand" te maken en een gelijkheidsvergelijking te gebruiken. Of directer, vergelijk de velden van het geretourneerde product met specifieke (hardgecodeerde) waarden in de test, nogmaals, zonder de EntityGenerator in de test te gebruiken.


3
2018-05-19 21:26



Uhhhhhhhhhhh ...................

V1: Maak geen wijzigingen in de code en schrijf een test. Schrijf eerst een test voor het verwachte gedrag. Dan kun je doen wat je wilt met de SUT.

V2: u maakt de wijzigingen niet in uw Product Gateway om de eigenaar van het product te veranderen. U maakt de wijziging in uw model.

Maar als u erop staat, luister dan naar uw tests. Ze vertellen je dat je de mogelijkheid hebt om producten van de gateway te halen die de verkeerde eigenaren hebben. Oeps, het lijkt op een bedrijfsregel. Moet worden getest in het model.

Ook gebruik je een mock. Waarom test u een implementatiedetail? De gateway geeft alleen maar om het _productRepository.GetProduct(id) geeft een product terug. Niet wat het product is.

Als u op deze manier test, maakt u fragiele tests. Wat als het product verder verandert. Nu heb je overal faaltests.

Uw consumenten van het product (MODEL) zijn de enige die zich zorgen maken over de implementatie van Product.

Dus je gateway-test zou er als volgt uit moeten zien:

[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();
}

Zet geen zakelijke logica waar het niet hoort! En het is logisch dat er niet getest wordt op bedrijfslogica waar er geen zou moeten zijn.


2
2018-05-08 16:19



Als u echt wilt garanderen dat de servicemethode de kenmerken van uw producten niet verandert, hebt u twee opties:

  • Definieer de verwachte productkenmerken in uw test en beweer dat het resulterende product overeenkomt met deze waarden. (Dit lijkt te zijn wat je nu doet door het object te klonen.)

  • Bespotten artikel en geef verwachtingen op om te verifiëren dat de servicemethode de kenmerken niet wijzigt.

Dit is hoe ik het laatste zou doen met NMock:

// 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();
}

1
2018-05-21 16:17



Mijn vorige antwoord staat, hoewel het ervan uitgaat dat de leden van de Productklasse waar u om geeft openbaar en virtueel zijn. Dit is niet waarschijnlijk als de klasse een POCO / DTO is.

Wat u zoekt, kan anders worden geformuleerd als een manier om de waarden (niet de instantie) van het object met elkaar te vergelijken.

Een manier om te vergelijken om te zien of ze overeenkomen wanneer ze in series zijn. Ik deed dit onlangs voor wat code ... Was het vervangen van een lange parameterlijst door een geparametreerd object. De code is crufty, ik wil het echter niet refacteren omdat het toch hoe dan ook snel zal verdwijnen. Dus ik doe deze serialisatie-vergelijking gewoon als een snelle manier om te zien of ze dezelfde waarde hebben.

Ik heb een aantal utility-functies geschreven ... Assert2.IsSameValue (expected, actual) die functioneert als NUnit's Assert.AreEqual (), behalve dat het serialiseert via JSON voordat het wordt vergeleken. Op dezelfde manier kan It2.IsSameSerialized () worden gebruikt om parameters te beschrijven die zijn doorgegeven aan spotgesprekken op een manier vergelijkbaar met Moq.It.Is ().

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;
        });
    }
}

1
2018-05-24 03:19



Welnu, een manier is om een ​​schijnproduct over te slaan in plaats van het eigenlijke product. Controleer niets om het product te beïnvloeden door het strikt te maken. (Ik neem aan dat je Moq gebruikt, het lijkt erop dat je het bent)

[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();
}

Dat gezegd hebbende, ik weet niet zeker of je dit zou moeten doen. De test doet te veel, en kan erop duiden dat er ergens een andere vereiste is. Zoek die vereiste en maak een tweede test. Misschien wil je gewoon voorkomen dat je iets stoms doet. Ik denk niet dat die schalen, want er zijn zoveel domme dingen die je kunt doen. Proberen elk te testen zou te lang duren.


0
2018-05-19 06:35



Ik weet het niet zeker, als de unit-test moet geven om "wat de gegeven methode doet niet"Er zijn ontelbare stappen die mogelijk zijn .In strikte de test" GetProduct (id) retourneert hetzelfde product als getProduct (id) op productRepository "is correct met of zonder de regel product.Owner = "totallyDifferentOwner".

U kunt echter een test maken (indien nodig) "GetProduct (id) retourneer product met dezelfde inhoud als getProduct (id) op productRepository" waar u een (waarlijk diepe) kloon van één productexemplaar kunt maken en vergelijk de inhoud van de twee objecten (dus geen object.Equals of object.ReferenceEquals).

De unit tests zijn geen garantie voor 100% foutloos en correct gedrag.


0
2018-05-19 06:50