Domanda

ho iniziato ad usare AutoFixture http://autofixture.codeplex.com/ come mio test di unità era gonfio con un sacco di messa a punto dei dati. Passavo più tempo in seting il backup dei dati che a scrivere il mio test di unità. Ecco un esempio di come il mio aspetto iniziali di unit test, come (esempio tratto dal campione di applicazione del carico da DDD blue book)

[Test]
public void should_create_instance_with_correct_ctor_parameters()
{
    var carrierMovements = new List<CarrierMovement>();

    var deparureUnLocode1 = new UnLocode("AB44D");
    var departureLocation1 = new Location(deparureUnLocode1, "HAMBOURG");
    var arrivalUnLocode1 = new UnLocode("XX44D");
    var arrivalLocation1 = new Location(arrivalUnLocode1, "TUNIS");
    var departureDate1 = new DateTime(2010, 3, 15);
    var arrivalDate1 = new DateTime(2010, 5, 12);

    var carrierMovement1 = new CarrierMovement(departureLocation1, arrivalLocation1, departureDate1, arrivalDate1);

    var deparureUnLocode2 = new UnLocode("CXRET");
    var departureLocation2 = new Location(deparureUnLocode2, "GDANSK");
    var arrivalUnLocode2 = new UnLocode("ZEZD4");
    var arrivalLocation2 = new Location(arrivalUnLocode2, "LE HAVRE");
    var departureDate2 = new DateTime(2010, 3, 18);
    var arrivalDate2 = new DateTime(2010, 3, 31);

    var carrierMovement2 = new CarrierMovement(departureLocation2, arrivalLocation2, departureDate2, arrivalDate2);

    carrierMovements.Add(carrierMovement1);
    carrierMovements.Add(carrierMovement2);

    new Schedule(carrierMovements).ShouldNotBeNull();
}

Ecco come ho cercato di refactoring con AutoFixture

[Test]
public void should_create_instance_with_correct_ctor_parameters_AutoFixture()
{
    var fixture = new Fixture();

    fixture.Register(() => new UnLocode(UnLocodeString()));

    var departureLoc = fixture.CreateAnonymous<Location>();
    var arrivalLoc = fixture.CreateAnonymous<Location>();
    var departureDateTime = fixture.CreateAnonymous<DateTime>();
    var arrivalDateTime = fixture.CreateAnonymous<DateTime>();

    fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
        (departure, arrival, departureTime, arrivalTime) => new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime));

    var carrierMovements = fixture.CreateMany<CarrierMovement>(50).ToList();

    fixture.Register<List<CarrierMovement>, Schedule>((carrierM) => new Schedule(carrierMovements));

    var schedule = fixture.CreateAnonymous<Schedule>();

    schedule.ShouldNotBeNull();
}

private static string UnLocodeString()
{
    var stringBuilder = new StringBuilder();

    for (int i = 0; i < 5; i++)
        stringBuilder.Append(GetRandomUpperCaseCharacter(i));

    return stringBuilder.ToString();
}

private static char GetRandomUpperCaseCharacter(int seed)
{
    return ((char)((short)'A' + new Random(seed).Next(26)));
}

Vorrei sapere se c'è modo migliore di refactoring. Vorrebbe farlo più breve e più facile di quello.

È stato utile?

Soluzione

Il primo tentativo sembra buono, ma ci sono almeno un paio di cose che si possono semplificare un po '.

Prima di tutto, si dovrebbe essere in grado di ridurre questo:

fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
    (departure, arrival, departureTime, arrivalTime) =>
        new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime));

a questo:

fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
    () => new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime));

dal momento che non si sta usando quelle altre variabili. Tuttavia, questo blocca essenzialmente qualsiasi creazione di CarrierMovement di utilizzare gli stessi quattro valori. Anche se ogni CarrierMovement creato sarà un'istanza separata, saranno tutti condividono gli stessi quattro valori, e mi chiedo se questo era quello che volevi dire?

Allo stesso modo di cui sopra, invece di

fixture.Register<List<CarrierMovement>, Schedule>((carrierM) =>
    new Schedule(carrierMovements));

è possibile scrivere

fixture.Register(() => new Schedule(carrierMovements));

dal momento che non si utilizza la variabile carrierM. Tipo inferenze capirà che si sta registrando una pianificazione a causa del tipo di ritorno della Funz.

Tuttavia, supponendo che gli sguardi Schedule costruttore come questo:

public Schedule(IEnumerable<CarrierMovement> carrierMovements)

si potrebbe invece aver appena registrato il carrierMovements in questo modo:

fixture.Register<IEnumerable<CarrierMovement>>(carrierMovements);

che causerebbe AutoFixture di risolvere automaticamente Schedule correttamente. Questo approccio è più gestibile perché permette di aggiungere un parametro al costruttore Schedule in futuro senza rompere il test (a patto che AutoFixture può risolvere il tipo di parametro).

Tuttavia, siamo in grado di fare di meglio in questo caso perché non abbiamo davvero usare la variabile carrierMovements di qualcosa di diverso di registrazione. Ciò di cui abbiamo veramente bisogno di fare è solo per dire AutoFixture come creare istanze di IEnumerable<CarrierMovement>. Se non si preoccupano il numero 50 (non si dovrebbe), possiamo anche utilizzare la sintassi Metodo Gruppo in questo modo:

fixture.Register(fixture.CreateMany<CarrierMovement>);

Si noti la mancanza di metodo parantheses invocazione:. Stiamo registrando un Func, e dal momento che il tipo di CreateMany<T> metodo IEnumerable<T> rendimenti inferenze si prende cura di tutto il resto

Tuttavia, questi sono tutti i dettagli. Su un livello più alto, si potrebbe prendere in considerazione la mancata registrazione CarrierMovement affatto. Assumendo questo costruttore:

public CarrierMovement(Location departureLocation,
    Location arrivalLocation,
    DateTime departureTime,
    DateTime arrivalTime)

autofixture dovrebbe essere in grado di capire da sé.

Si creerà una nuova istanza per ogni posizione departureLocation e arrivalLocation, ma questo è niente di diverso da quello che hai fatto manualmente nella prova iniziale.

Quando si tratta di tempi, di default usa AutoFixture DateTime.Now, che almeno assicura che l'orario di arrivo non sarà mai prima dell'orario di partenza. Tuttavia, essi sono molto probabile che sia identico, ma si può sempre registrare una funzione di incremento automatico se questo è un problema.

A fronte di tali considerazioni, ecco un'alternativa:

public void should_create_instance_with_correct_ctor_parameters_AutoFixture()
{
    var fixture = new Fixture();

    fixture.Register(() => new UnLocode(UnLocodeString()));

    fixture.Register(fixture.CreateMany<CarrierMovement>);

    var schedule = fixture.CreateAnonymous<Schedule>();

    schedule.ShouldNotBeNull();
}

Per risolvere il problema con IList<CarrierMovement> è necessario registrarlo. Ecco un modo per farlo:

fixture.Register<IList<CarrierMovement>>(() =>
    fixture.CreateMany<CarrierMovement>().ToList());

Tuttavia, dal momento che si chiede, voglio dire che gli sguardi Schedule costruttore come questo:

public Schedule(IList<CarrierMovement> carrierMovements)

e credo davvero che si dovrebbe riconsiderare la modifica che API per assumere un IEnumerable<Carriemovement>. Da un punto di vista progettuale API, fornendo una raccolta tramite uno dei componenti (compreso un costruttore) implica che il membro è autorizzato a modificare l'insieme (ad esempio richiamandolo di aggiungere, rimuovere e metodi Cancella). Questo è difficilmente il comportamento che ci si aspetta da un costruttore, in modo da non lo permette.

AutoFixture genererà automaticamente i nuovi valori per tutti Location oggetti nel mio esempio di cui sopra, ma a causa della velocità della CPU, le successive istanze di DateTime sono suscettibili di essere identici.

Se si vuole aumentare DateTimes, è possibile scrivere una piccola classe che incrementi l'restituiti DateTime ogni volta che viene invocato. Lascio l'attuazione di quella classe per il lettore interessato, ma si potrebbe poi registrarlo in questo modo:

var dtg = new DateTimeGenerator();
fixture.Register(dtg.Next);

assumendo questa API (avviso ancora una volta la sintassi Metodo Gruppo sopra):

public class DateTimeGenerator
{
    public DateTime Next();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top