2014-12-18 6 views
7

Ich lese eine Menge Dokumentation und Beispiele darüber, wie man Unit-Tests richtig kombiniert, indem man die drei Komponenten im Titel kombiniert. Ich habe eine Testmethode für meine Geschäftslogik entwickelt, aber sie fühlt sich sehr klobig und dreckig an.XUnit, AutoFixture und Moq Best Practice

Ich möchte gerne Feedback von Leuten bekommen, die mehr über dieses Thema erfahren haben, um zu sehen, wie ich es verbessern kann.

Hier ist der Code, Erklärung folgt:

[Fact] 
public void ShouldGetItemWithSameId() 
{ 
    var fixture = new Fixture().Customize(new AutoMoqCustomization()); 
    var facade = fixture.Freeze<Mock<IDataFacade>>(); 
    facade.Setup(c => c.Get(It.IsAny<int>())).Returns((int i) => new Item { Key = i }); 

    var sut = fixture.Create<BusinessLogic>(); 
    var expected = fixture.Create<int>(); 

    Assert.Equal(expected, sut.Get(expected).Key); 
} 

Meine BusinessLogic Klasse nimmt ein IDataFacade als Konstruktor-Parameter, der in seinen Get(int) Verfahren zuständig ist das Element mit der gleichen Id, ziemlich einfach Sachen zu holen.

Ich friere die IDataFacade Mock ein und ich habe es eingerichtet, um ein Objekt mit der ID in It.IsAny<int> zu konstruieren. Ich erstelle dann mein SUT und teste es. Funktioniert gut.

Ich mag würde zu verstehen, wenn ich die Dinge verbessern kann unter Berücksichtigung der folgenden:

  • Ich habe komplexere Methoden zu testen, wie ein Query Methode, die eine Klasse enthält eine Menge Eigenschaften nimmt die verwendet wird, als Filter für übereinstimmende Eigenschaften des abgefragten Typs. In diesem Fall würde ich nicht wissen, wie man den "Setup" -Teil des Mocks richtig macht, da ich alle oder fast alle Eigenschaften des zurückgegebenen Typs initialisieren muss, und in diesem Szenario ist es kein einzelnes Item eine ganze Sammlung
  • Die Setup-Teil fehl am Platz fühlt, würde ich mag es wiederzuverwenden in mehr Methoden

ich habe in der Lage sein, einige anderen Tests mit Theory mit AutoMoqData aber ich war nicht in der Lage, diesen Test zu erreichen (und ich denke, die komplexeren) mit diesem Ansatz, so wechselte ich wieder auf Ebene Fact mit manuell instanziierten Fixture.

Jede Hilfe wird sehr geschätzt.

+0

Habt Ihr (Auto) NSubstitute in Betracht gezogen - Ich habe zu lange an meiner 'Was ist falsch mit Moq' Haltung festgehalten. http://weareadaptive.com/blog/2014/09/30/why-nsubstitute/ –

Antwort

5

Ihr Test sieht für mich in Ordnung, obwohl ich eine Änderung empfehlen würde.Die folgende Zeile verschärft werden könnte, um nur den erwarteten Wert zurück, wenn der erwartete Wert übergeben wird:

facade.Setup(c => c.Get(It.IsAny<int>())).Returns((int i) => new Item { Key = i }); 

Alles, was Sie brauchen würden, um es das erwartete Variable zu tun bewegen und die Is.IsAny ändern wie folgt:

var expected = fixture.Create<int>(); 
facade.Setup(c => c.Get(expected)).Returns((int i) => new Item { Key = i }); 

I haben komplexere Methoden zu testen, wie eine Query-Methode, die eine Klasse, die eine Menge von Eigenschaften führt, die auf passende Eigenschaften von der Art als Filter verwendet werden, abgefragt wird. In diesem Fall würde ich nicht wissen, wie man den "Setup" -Teil des Mocks richtig macht, da ich alle oder fast alle Eigenschaften des zurückgegebenen Typs initialisieren muss, und in diesem Szenario ist es kein einzelnes Item eine ganze Sammlung

Ich glaube nicht, dass Sie alle Werte auf dem zurückgegebenen Typ initialisieren müssten. Ich schätze, Ihre DataFacade gibt ein Objekt (oder eine Liste von in diesem Fall) zurück? Alles, was Sie tun müssen, ist sicherzustellen, dass die zurückgegebenen Objekte mit den Referenzen der von der DataFacade zurückgegebenen Objekte übereinstimmen, Sie müssen sich keine Sorgen um Eigenschaften usw. machen, da Sie die Konstruktion dieser Objekte nicht gerade testen ist zurückgekommen. Wenn ich falsch verstanden habe und Sie die Objekte in BusinessLogic konstruieren, dann ist das eine andere Sache. Ich persönlich hätte die Geschäftslogik nicht von der Datenschicht abhängig, aber das ist eine andere Diskussion. :-)

Das Setup-Teil fehl am Platz fühlt, würde Ich mag es

in mehreren Verfahren zur Wiederverwendung in der Lage sein

Sie können. Entweder extrahieren Sie es zu einer separaten Methode oder, wenn es für jeden Test in der Klasse anwendbar ist, legen Sie es in eine Setup-Methode. Ich bin mit XUnit nicht vertraut, aber jedes andere Testframework, das ich verwendet habe, bietet die Möglichkeit, ein gemeinsames Setup durchzuführen, so dass ich bezweifle, dass XUnit anders sein wird.

Und mein abschließender Kommentar, behandeln Sie Ihren Testcode, wie Sie Ihren Produktionscode behandeln würden, wenn es ein Chaos aussieht, Dinge zu tun, um es besser zu machen. Tests eignen sich hervorragend, um das Verhalten eines Systems zu beschreiben. Wenn sie jedoch schwer zu lesen sind (und beibehalten), verlieren Sie viel Wert.

Edit, stellt sich heraus, das ist nicht mein letzter Kommentar! Wenn Sie neu bei TDD sind, was ich nicht sicher bin, sollten Sie nicht in die Falle gehen, jede einzelne Klasse in Ihrer Anwendung zu testen. Es ist ein weit verbreitetes Muster, das TDD meiner Meinung nach abwertet . Ich habe eine blog post on my feelings geschrieben und Ian Cooper hat eine superb presentation auf die Frage gegeben.

+0

Ein nettes kleines Wrapper zu AutoFixture create() ist [tdd-toolkit] (https://github.com/grzesiek-galezowski/tdd-toolkit) so 'var erwartet = fixture.Create ();' wird 'var erwartet = Any.Integer();' –

+0

@ Robi-y Wrapper ist ein bisschen eine Strecke; während ich sicher bin, dass es seinen Platz hat, adressiert es nicht einen Stapel von Sachen, die das OP will, also warum die Verwirrung zweier Sätze von Syntax/Konvention einführen. –

+0

@RubenBartelink stimmen im Allgemeinen zu, aber in einigen Fällen half es mir, einen etwas klareren Code zu erreichen, vielleicht ist ein besseres Beispiel, zB Any.IntegerOtherThan (42) das sie natürlich nicht brauchte - nur einen winzigen syntaktischen Zucker vorschlagend was manchmal hilfreich sein kann, danke –

2

Einige Grundlagen:

Ihre Testklasse instanziiert (und dessen Konstruktor aufgerufen) vor jedem einzelnen Test ausgeführt wird. z.B. Wenn Ihre Testklasse hat drei Methoden mit [Fact] Attribut, es dreimal instanziiert wird

A TestFixture Klasse ist eine andere Klasse, die eine einziges Mal zu instanziiert gemeint ist für alle Tests in der Testklasse.

Damit dies funktioniert, muss Ihre Testklasse die IUseFixture-Schnittstelle implementieren, z. Implementieren eines Mitglieds SetFixture()

Sie können dieselbe MyTestFixture-Klasse für mehrere Testklassen verwenden.

In der TestFixture machen Sie alle Mock-Setups.

Hier ist das allgemeine Layout:

public class MyTestFixture 
{  
    public Mock<MyManager> ManagerMock; 

    public TestFixture() // runs once 
    { 
     ManagerMock.Setup(...); 
    } 
} 

public MyTestClass : IUseFixture<MyTestFixture> 
{ 
    private MyTestFixture fixture; 

    public MyTestClass() 
    { 
     // ctor runs for each [Fact] 
    } 

    public void SetFixture(MyTestFixture fixture) 
    { 
     this.fixture = fixture; 
    } 

    [Fact] 
    public void MyTest 
    { 
     // use Mock 
     fixture.ManagerMock.DoSomething() 
    } 
} 
+0

Ich kenne bereits all diese Sachen, aber ich denke, mit der IUseFixture-Schnittstelle Art besiegt den Zweck der AutoFixture. Ich könnte mich jedoch irren. –

+0

"Innerhalb der TestFixture machen Sie alle Mock-Setups" - Ich stimme nicht zu, Sie sollten nur alle Einstellungen vornehmen, die für alle Tests in diesem Gerät gelten. – DoctorMick

+0

@DoctorMick ja, natürlich, in der TestFixture machst du (nur) alles was du brauchst für ** alle Tests ** – DrKoch

7

Insgesamt sieht der ursprüngliche Test gut aus. Es ist nicht möglich und auch nicht einfach, das Setup von Stubs and Mocks aus dem Test generisch zu extrahieren.

Was Sie können obwohl tun, ist die Anordnen Phase des Tests zu minimieren.Hier ist die Original-Test neu geschrieben mit AutoFixture.Xunit den eigenen Komponententests DSL:

[Theory, TestConventions] 
public void ShouldGetItemWithSameId(
    [Frozen]Mock<IDataFacade> facadeStub, 
    BusinessLogic sut, 
    int expected) 
{ 
    facadeStub 
     .Setup(c => c.Get(It.IsAny<int>())) 
     .Returns((int i) => new Item { Key = i }); 

    var result = sut.Get(expected); 
    var actual = result.Key; 

    Assert.Equal(expected, actual); 
} 

Das TestConventions Attribut ist wie folgt definiert:

public class TestConventionsAttribute : AutoDataAttribute 
{ 
    public TestConventionsAttribute() 
     : base(new Fixture().Customize(new AutoMoqCustomization())) 
    { 
    } 
} 

HTH


Probenarten verwendet in das Beispiel:

public class Item 
{ 
    public int Key { get; set; } 
} 

public interface IDataFacade 
{ 
    Item Get(int p); 
} 

public class BusinessLogic 
{ 
    private readonly IDataFacade facade; 

    public BusinessLogic(IDataFacade facade) 
    { 
     this.facade = facade; 
    } 

    public Item Get(int p) 
    { 
     return this.facade.Get(p); 
    } 
} 
+1

Das mag ich. Ich werde es ausprobieren und später kommentieren. Vielen Dank :) –