2014-12-18 29 views
7

Sto leggendo molta documentazione ed esempi su come testare correttamente le cose combinando i tre componenti nel titolo. Ho trovato un metodo di prova per un metodo sulla mia logica aziendale, ma mi sembra molto goffo e sporco.XUnit, AutoFixture e Moq best practice

Vorrei ricevere un feedback da persone più esperte su questo argomento per vedere come posso migliorarlo.

Ecco il codice, segue la spiegazione:

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

La mia classe BusinessLogic prende un IDataFacade come parametro del costruttore, che è responsabile nel suo metodo Get(int) per recuperare la voce con lo stesso ID, roba piuttosto semplice.

Congelando il modello IDataFacade e l'ho configurato per creare un oggetto che corrisponda all'ID in It.IsAny<int>. Quindi creo il mio SUT e lo collaudo. Funziona bene.

Mi piacerebbe capire se riesco a migliorare le cose considerando quanto segue:

  • devo testare metodi più complessi, come un metodo Query che prende una classe che contiene un sacco di proprietà che verrà utilizzato come filtri sulle proprietà corrispondenti sul tipo interrogato. In questo caso non saprei come fare correttamente la parte "Setup" del mock, dal momento che devo inizializzare tutti, o chiudere a tutti, le proprietà del tipo restituito, e in questo scenario non è un singolo Item ma un'intera collezione
  • parte
  • Il setup si sente fuori posto, mi piacerebbe essere in grado di riutilizzarla in più metodi

ho alcuni altri test che utilizzano Theory con AutoMoqData ma ero in grado di raggiungere questo test (e penso che quelli più complessi) utilizzano quell'approccio, così sono tornato al semplice Fact con il dispositivo istanziato manualmente.

Qualsiasi aiuto sarà estremamente apprezzato.

+0

Hai considerato (Auto) NSubstitute: ho tenuto duro per troppo tempo sul mio atteggiamento di defilatura di "cosa c'è che non va". http://weareadaptive.com/blog/2014/09/30/why-nsubstitute/ –

risposta

5

tua prova guarda bene a me, anche se mi sento di raccomandare un cambio.La riga seguente potrebbe essere serrato fino a solo restituire il valore previsto se viene passato il valore atteso:

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

Tutto quello che avresti bisogno di farlo spostare la variabile previsto e cambiare l'Is.IsAny in questo modo:

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

devo testare metodi più complessi, come un metodo query che prende una classe contenente un sacco di proprietà che verranno utilizzati come filtri sulla corrispondenza proprietà sul tipo interrogato. In questo caso non saprei come fare correttamente la parte "Setup" del mock, dal momento che devo inizializzare tutti, o chiudere a tutti, le proprietà del tipo restituito, e in questo scenario non è un singolo Item ma un'intera collezione

Non penso che sarebbe necessario inizializzare tutti i valori sul tipo restituito. Immagino che il tuo DataFacade restituisca un oggetto (o un elenco di in questo caso)? Tutto quello che devi fare è assicurarti che gli oggetti restituiti corrispondano ai riferimenti di quelli restituiti da DataFacade, non devi preoccuparti delle proprietà ecc. Poiché non stai testando la costruzione di questi oggetti, solo che sono restituito. Se ho frainteso e stai costruendo gli oggetti in BusinessLogic, allora è diverso. Personalmente, non avrei la logica aziendale dipendente dal livello dati, ma questa è una discussione diversa. :-)

parte La configurazione si sente fuori posto, mi piacerebbe essere in grado di riutilizzarla in più metodi

È possibile. Estrarlo in un metodo separato o, se è applicabile a tutti i test della classe, inserirlo in un metodo di installazione. Non ho familiarità con XUnit, ma ogni altro framework di test che ho usato fornisce la possibilità di fare impostazioni comuni, quindi dubito che XUnit sarà diverso.

E il mio ultimo commento, tratta il tuo codice di test come se dovessi trattare il tuo codice di produzione, se sembra un disastro fare le cose per renderlo migliore. I test sono ottimi per descrivere il comportamento di un sistema, ma se sono difficili da leggere (e mantenere) perdi molto valore.

Modifica, risulta che non è il mio commento finale! Se sei nuovo a TDD, che non sono sicuro di te, non cadere nella trappola di testare ogni singola classe nella tua applicazione, è un modello comune che è diventato prevalente e svaluta il TDD secondo me . Ho scritto uno blog post on my feelings e Ian Cooper ha dato un superb presentation sull'argomento.

+0

Un bel piccolo wrapper per createFix di AutoFixture è [tdd-toolkit] (https://github.com/grzesiek-galezowski/tdd-toolkit) quindi 'var expected = fixture.Create ();' diventa 'var expected = Any.Integer();' –

+0

@ wrapper robi-y è un po 'allungato; mentre sono sicuro che ha il suo posto, non affronta una pila di cose che l'OP vuole così perché introdurre la confusione di due gruppi di sintassi/convenzione. –

+0

@RubenBartelink generalmente sono d'accordo, ma in alcuni casi mi ha aiutato a ottenere un codice un po 'più chiaro, forse un esempio migliore è, ad esempio, Any.IntegerOtherThan (42) che ovviamente non era necessario - solo suggerendo qualche piccolo zucchero sintattico che a volte può essere utile, grazie –

2

Alcuni principi fondamentali:

tua classe di prova viene creata un'istanza (e il suo costruttore chiamato) prima di ogni singolo test viene eseguito. per esempio. se la classe di test ha tre metodi con [Fact] attributo, viene istanziato tre volte

classe A TestFixture è un'altra classe che è destinato ad essere istanziare un sola volta per tutte le prove della tua classe di test.

Per rendere questo lavoro, la classe di test deve implementare l'interfaccia IUseFixture, ad es. implementare un membro SetFixture()

È possibile utilizzare la stessa classe MyTestFixture per diverse classi di test.

All'interno di TestFixture si eseguono tutte le impostazioni di simulazione.

Qui il layout generale:

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

Conosco già tutte queste cose, ma penso che l'utilizzo dell'interfaccia IUseFixture tipo sconfigge lo scopo di avere AutoFixture. Potrei sbagliarmi comunque. –

+0

"All'interno di TestFixture esegui tutte le impostazioni di simulazione" - Non sono d'accordo, dovresti eseguire solo le impostazioni applicabili a tutti i test in quell'attrezzatura. – DoctorMick

+0

@DoctorMick sì, naturalmente, nel TestFixture fai (solo) tutto ciò di cui hai bisogno per ** tutti i test ** – DrKoch

7

Nel complesso, il test originale sembra buono. Non è possibile né facile estrarre la configurazione di Stubs and Mocks dal test, in modo generico.

Cosa si può fare fare, è minimizzare la fase Arrange del test.Ecco la prova originale ri-scritto usando s' AutoFixture.Xunit propria unità-test 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); 
} 

L'attributo TestConventions è definito come:

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

HTH


tipi campione utilizzato nella nell'esempio:

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

Mi piace. Lo proverò e commenterò più tardi. Grazie :) –