2013-03-15 16 views
6

dato questo sistema di test:Teorie Autodata con AutoFixture utilizzando falsi manuali

public class MySut 
{ 
    private readonly IHardToMockDependency _hardToMockDependency; 

    public MySut(IHardToMockDependency hardToMockDependency, 
       IOtherDependency otherDependency) 
    { 
     _hardToMockDependency = hardToMockDependency; 
    } 

    public string GetResult() 
    { 
     return _hardToMockDependency.GetResult(); 
    } 
} 

public interface IOtherDependency { } 

public interface IHardToMockDependency 
{ 
    string GetResult(); 
} 

e questo test di unità:

internal class FakeHardToMockDependency : IHardToMockDependency 
{ 
    private readonly string _result; 

    public FakeHardToMockDependency(string result) 
    { 
     _result = result; 
    } 

    public string GetResult() 
    { 
     return _result; 
    } 
} 

public class MyTests 
{ 
    [Fact] 
    public void GetResultReturnsExpected() 
    { 
     string expectedResult = "what I want"; 
     var otherDependencyDummy = new Mock<IOtherDependency>(); 
     var sut = new MySut(new FakeHardToMockDependency(expectedResult), 
          otherDependencyDummy.Object); 

     var actualResult = sut.GetResult(); 

     Assert.Equal(expectedResult, actualResult); 
    } 
} 

Come dovrei convertirlo in uso AutoFixture.Xunit e AutoFixture. AutoMoq (mentre si utilizza ancora il falso manuale)?

Nei test del mondo reale, il falso manuale avrebbe un'interfaccia e un comportamento più complessi. Si noti che voglio passare la variabile anonima (la stringa expectedResult) al costruttore del fake manuale.

risposta

8

Ci sono già alcune buone risposte qui, ma mi piacerebbe suggerire un'alternativa più semplice che comporta un allentamento leggermente invariante della classe FakeHardToMockDependency. Renderlo pubblico, e fornire un modo per assegnare il risultato che viene disaccoppiato dal costruttore:

public class FakeHardToMockDependency : IHardToMockDependency 
{ 
    private string _result; 

    public FakeHardToMockDependency(string result) 
    { 
     _result = result; 
    } 

    internal string Result 
    { 
     get { return _result; } 
     set { _result = value; } 
    } 

    public string GetResult() 
    { 
     return _result; 
    } 
} 

Si noti che ho aggiunto una proprietà interna e rimosso la parola chiave readonly dal campo.

Ciò consente di refactoring la prova originale a questo:

[Theory, AutoMoqData] 
public void GetResultReturnsExpected_AutoDataVersion(
    [Frozen(As = typeof(IHardToMockDependency))]FakeHardToMockDependency fake, 
    MySut sut) 
{ 
    var expected = "what I want"; 
    fake.Result = expected; 

    var actual = sut.GetResult(); 

    Assert.Equal(expected, actual); 
} 

Per completezza, ecco il codice AutoMoqDataAttribute:

public class AutoMoqDataAttribute : AutoDataAttribute 
{ 
    public AutoMoqDataAttribute() 
     : base(new Fixture().Customize(new AutoMoqCustomization())) 
    { 
    } 
} 
+0

Ora vai e prendi il martello d'oro dalla mia mano. ;-) Ma che differenza fa 'internal' qui, al contrario di 'public'? Il SUT non può vedere la proprietà in entrambi i casi, perché ottiene solo l'interfaccia. – TeaDrivenDev

+0

@GCATNM Non importa se la proprietà Result è 'internal' o' public'. Personalmente, lo renderei pubblico, ma l'OP forniva il falso come una classe "interna", quindi volevo solo cambiare il meno possibile per rendere le modifiche suggerite più accettabili :) –

+0

Wow, tre grandi risposte, e uno dall'autore stesso! Allentare gli invarianti del falso va bene. Grazie! Accettare solo una risposta sarà dura - dovrò provarle. – TrueWill

3

forse questo non è l'impostazione più idiomatica Autofixture ma sicuramente funziona:

[Fact] 
public void GetResultReturnsExpected() 
{ 
    var fixture = new Fixture() 
     .Customize(new AutoMoqCustomization()); 

    var expectedResult = fixture.Create<string>(); 

    fixture.Register<IHardToMockDependency>(
     () => new FakeHardToMockDependency(expectedResult)); 

    var sut = fixture.Create<MySut>(); 

    var actualResult = sut.GetResult(); 

    Assert.Equal(expectedResult, actualResult); 
} 

Se si desidera utilizzare anche AutoData è possibile creare il proprio AutoMoqData sulla base di this great article dove è possibile nascondere alcune o tutte le fixture custimizations.

Qualcosa di simile:

public class MySutAutoDataAttribute : AutoDataAttribute 
{ 
    public MySutAutoData() 
     : base(new Fixture() 
      .Customize(new AutoMoqCustomization())) 
    { 
     Fixture.Freeze<string>(); 

     Fixture.Register<IHardToMockDependency>(
      () => new FakeHardToMockDependency(Fixture.Create<string>())); 
    } 
} 

E si può utilizzare come:

[Theory, MySutAutoData] 
public void GetResultReturnsExpected(MySut sut, string expectedResult) 
{ 
    var actualResult = sut.GetResult(); 

    Assert.Equal(expectedResult, actualResult); 
} 

Ma si dovrebbe notare che v'è spazio per un sacco di miglioramento nel MySutAutoDataAttribute per esempio: non è molto generico e Fixture.Freeze<string>(); può causare problemi se si utilizzano più stringhe nei test.

+0

mi rendo conto che potrei farlo, ma la mia domanda è specificamente di fare questo con un discendente di AutoDataAttribute. Potresti fornire un esempio per farlo in questo caso? – TrueWill

+0

@ TrueSpiacente mi è sfuggito il titolo che si desidera una soluzione con AutoDataAttribute. Ho aggiornato la mia risposta con una possibile implementazione. – nemesv

+0

Tuttavia, congelare la stringa in questo modo non ti dà alcun controllo. – TeaDrivenDev

4

A seconda del tipo di parametri che è necessario passare al falso manuale, è possibile utilizzare un attributo parametrizzato, simile a quello predefinito di AutoFixture .

Alla luce di questi

public interface IHardToMockDependency 
{ 
    string Value { get; } 
} 

public class FakeHardToMockDependency : IHardToMockDependency 
{ 
    private readonly string _value; 

    public FakeHardToMockDependency(string value) 
    { 
     _value = value; 
    } 

    #region IHardToMockDependency Members 

    public string Value 
    { 
     get { return this._value; } 
    } 

    #endregion IHardToMockDependency Members 
} 

si crea un ICustomization implementazione che racconta un oggetto apparecchio come creare Un'implementazione dell'interfaccia IHardToFakeDependency:

public class FakeHardToMockDependencyCustomization : ICustomization 
{ 
    private readonly string _value; 

    public FakeHardToMockDependencyCustomization(string value) 
    { 
     _value = value; 
    } 

    #region ICustomization Members 

    public void Customize(IFixture fixture) 
    { 
     fixture.Register<IHardToMockDependency>(() => new FakeHardToMockDependency(this._value)); 
    } 

    #endregion ICustomization Members 
} 

Si noti che questo ha bisogno di sapere la stringa che si desidera passare, ovviamente.

Successivamente, è arrotolare con le altre personalizzazioni che si desidera utilizzare in un CompositeCustomization:

public class ManualFakeTestConventions : CompositeCustomization 
{ 
    public ManualFakeTestConventions(string value) 
     : base(new FakeHardToMockDependencyCustomization(value), new AutoMoqCustomization()) 
    { 
    } 
} 

Assicurati sempre di mettere le personalizzazioni in ordine dal più specifico al più generale, come spiegato here da Mark Seemann.

Ora si crea un AutoDataAttribute applicazione che utilizza questa personalizzazione:

public class ManualFakeAutoDataAttribute : AutoDataAttribute 
{ 
    public ManualFakeAutoDataAttribute(string value) 
     : base(new Fixture().Customize(new ManualFakeTestConventions(value))) 
    { 
    } 
} 

Questo può ora essere utilizzato allo stesso modo come un InlineAutoDataAttribute:

public class ManualFakeTests 
{ 
    [Theory, ManualFakeAutoData("iksdee")] 
    public void ManualFake(IHardToMockDependency fake) 
    { 
     Assert.IsType<FakeHardToMockDependency>(fake); 
     Assert.Equal("iksdee", fake.Value); 
    } 
} 

Si può anche avere iniettato in un auto-creato esempio SUT subito applicando l'attributo [Frozen] al parametro Teoria:

[Theory, ManualFakeAutoData("iksdee")] 
    public void SutWithManualFake([Frozen] IHardToMockDependency fake, MySut sut) 
    { 

    } 

questo creerà un'istanza MySut e l'istanza IHardToMockDependency necessario per il costruttore, per il quale avete dato AutoFixture una regola nel FakeHardToMockDependencyCustomization, e anche darvi quel caso come variabile fake.

Nota che il non blocco del falso ti darebbe comunque un'istanza corretta FakeHardToMockDependency e ne inserirò uno nel sut, ma questi sarebbero distinti, dato che abbiamo registrato un delegato di fabbrica nella personalizzazione. Il blocco dell'istanza fa sì che l'apparecchiatura restituisca sempre la stessa istanza per le successive richieste per l'interfaccia.

Questo ha alcune avvertenze, però:

  • Si hanno alcun riferimento alla stringa si passa come un parametro, in modo da avere in là come una stringa letterale due volte. Puoi aggirare questo con costanti di stringa all'interno della classe di test, per esempio.
  • Il numero di tipi che possono essere utilizzati come parametri di attributo NET è limitata. Finché è necessario solo tipi di base, si dovrebbe andare bene, ma chiamando costruttori o simili nella lista dei parametri non è possibile.
  • Dovreste solo utilizzare questo attributo quando si ha bisogno di un esempio se IHardToFakeDependency; altrimenti dovrai sempre passare un parametro stringa comunque. Se si dispone di una serie di personalizzazioni standard, è necessario utilizzare, creare un altro attributo che contiene solo quelle.
  • Se avete bisogno le capacità del InlineAutoDataAttribute, allo stesso tempo, è anche necessario creare un altro attributo che unisce le caratteristiche di entrambi.

A seconda delle circostanze concrete, si potrebbe anche voler guardare xUnit.net di PropertyDataAttribute, ma ho quasi mai trovare me stesso utilizzando tale.

In generale, a mio parere, capire come lavorare con le personalizzazioni e gli attributi di autodata e quando e come crearne uno sono la chiave per utilizzare in modo efficace AutoFixture e davvero far sì che vi risparmi lavoro.

Se si scrive spesso codice in un dominio specifico che è necessario testare, potrebbe essere opportuno creare una libreria contenente personalizzazioni, attributi e oggetti stub che saranno sempre pronti per l'uso dopo averlo trascinato accanto a xUnit .net, AutoFixture e Moq. So di essere dannatamente felice di aver costruito il mio.

Oh, e anche: Avere una dipendenza difficile da simulare potrebbe essere il punto in un problema di progettazione. Perché è così difficile deridere?

+0

Grazie; molto utile! E sono d'accordo che si tratta di un odore di codice che indica un potenziale problema di progettazione. – TrueWill