2013-07-25 13 views
6

Come è possibile impostare un test deterministico per verificare che gli articoli in un elenco siano ordinati?ordine di test unità di articoli in un elenco

Prima ho fatto la seguente:

public void SyncListContainsSortedItems(
    [Frozen] SyncItem[] expected, 
    SyncItemList sut) 
{ 
    Assert.Equal(expected.OrderBy(x => x.Key).First(), sut.First()); 
} 

Ma come con tutti i buoni test, ho guardato per un guasto prima di modificare il mio codice. Certo, questo è riuscito per fortuna, e poi è fallito. Quindi il mio fallimento iniziale non è deterministico.

In secondo luogo ho fatto il seguente, il pensiero, 'sicuramente questo garantirà un fallimento':

public void SyncListContainsSortedItems(
    [Frozen] SyncItem[] seed, 
    SyncItemList sut) 
{ 
    var expected = seed.OrderByDescending(x => x.Key); 
    Assert.Equal(expected.OrderBy(x => x.Key).First(), sut.First()); 
} 

Con mia grande sorpresa, inoltre, non ha fornito un fallimento deterministico. Ho capito che è perché il seme congelato potrebbe essere naturalmente creato in ordine decrescente per cominciare, quindi non ho davvero migliorato la situazione.

In questo momento, la mia implementazione non ordina gli articoli che passano attraverso il costruttore. Come posso stabilire una solida base per il mio test?

Ulteriori informazioni Il codice dell'elenco degli elementi di sincronizzazione è mostrato di seguito. La sua non è tanto quanto lo è il design sto esplorando:

public class SyncItemList : List<SyncItem> 
{ 
    public SyncItemList(SyncItem[] input) 
    { 
     foreach (var item in input) { this.Add(item); } 
    } 
} 

Aggiornamento ho sviluppato il test. Il seguente funziona ma con alta verbosità.

public void SyncListContainsSortedItems(IFixture fixture, List<SyncItem> seed) 
{ 
    var seconditem = seed.OrderBy(x => x.Key).Skip(1).First(); 
    seed.Remove(seconditem); 
    seed.Insert(0, seconditem); 
    var seedArray = seed.ToArray(); 

    var ascending = seedArray.OrderBy(x => x.Key).ToArray(); 
    var descending = seedArray.OrderByDescending(x => x.Key).ToArray(); 
    Assert.NotEqual(ascending, seedArray); 
    Assert.NotEqual(descending, seedArray); 

    fixture.Inject<SyncItem[]>(seedArray); 
    var sut = fixture.Create<SyncItemList>(); 

    var expected = ascending; 
    var actual = sut.ToArray(); 
    Assert.Equal(expected, actual); 
} 

Un modo semplice per alterare la mia implementazione per farlo passare è quello di ereditare da SortedSet<SyncItem> invece di List<SyncItem>.

+0

Come appare il 'SyncItemList'? –

+0

Sapete cosa sarebbe veramente interessante se ci fosse un attributo e un tipo di richiesta Non Ordinato, una specie di sequenza finita. Avresti solo bisogno di 3 o più oggetti unici e potresti garantire una sequenza fuori sequenza. – cocogorilla

+0

Posso chiedere quale versione di AutoFixture stai usando? [I numeri sono casuali] (https://github.com/AutoFixture/AutoFixture/wiki/AutoFixture-3.0-Release-Notes#numbers-are-random) in AutoFixture 3. –

risposta

10

Ci sono vari modi per farlo.

versione imperativo

Ecco una versione più semplice imperativo di quella prevista nel PO:

[Fact] 
public void ImperativeTest() 
{ 
    var fixture = new Fixture(); 
    var expected = fixture.CreateMany<SyncItem>(3).OrderBy(si => si.Key); 
    var unorderedItems = expected.Skip(1).Concat(expected.Take(1)).ToArray(); 
    fixture.Inject(unorderedItems); 

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

    Assert.Equal(expected, sut); 
} 

Mentre the default number of many items is 3, penso che è meglio chiamarlo esplicitamente in questo tipo di test. L'algoritmo di scrambling utilizzato qui trae vantaggio dal fatto che dopo ordina una sequenza di tre (distinti) elementi, spostando il primo elemento sul retro deve corrispondere a un elenco non ordinato..

Tuttavia, il problema con questo approccio è che si basa sulla mutazione del fixture, quindi è difficile da rifattorizzare un approccio più dichiarativo.

Versione personalizzata

Nel tentativo di refactoring per una versione più dichiarativo, è possibile prima incapsulare l'algoritmo di scrambling in a Customization:

public class UnorderedSyncItems : ICustomization 
{ 
    public void Customize(IFixture fixture) 
    { 
     fixture.Customizations.Add(new UnorderedSyncItemsGenerator()); 
    } 

    private class UnorderedSyncItemsGenerator : ISpecimenBuilder 
    { 
     public object Create(object request, ISpecimenContext context) 
     { 
      var t = request as Type; 
      if (t == null || 
       t != typeof(SyncItem[])) 
       return new NoSpecimen(request); 

      var items = ((IEnumerable)context 
       .Resolve(new FiniteSequenceRequest(typeof(SyncItem), 3))) 
       .Cast<SyncItem>(); 
      return items.Skip(1).Concat(items.Take(1)).ToArray(); 
     } 
    } 
} 

risolvere una new FiniteSequenceRequest(typeof(SyncItem), 3)) è semplicemente il debolmente tipizzato (non generico) modo di creare una sequenza finita di istanze SyncItem; è quello che fa CreateMany<SyncItem>(3) dietro le quinte.

Ciò consente di refactoring del test per:

[Fact] 
public void ImperativeTestWithCustomization() 
{ 
    var fixture = new Fixture().Customize(new UnorderedSyncItems()); 
    var expected = fixture.Freeze<SyncItem[]>().OrderBy(si => si.Key); 

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

    Assert.Equal(expected, sut); 
} 

Si noti l'uso del metodo Freeze. Ciò è necessario, poiché la personalizzazione UnorderedSyncItems modifica solo le istanze create da SyncItem[]; crea ancora un nuovo array ogni volta che riceve una richiesta di farlo. Freeze assicura che lo stesso array venga riutilizzato ogni volta, anche quando fixture crea l'istanza sut.

convenzione basata test

La prova di cui sopra può essere riscritta ad una prova dichiarativa, convenzione basata, con l'introduzione di un attributo [UnorderedConventions]:

public class UnorderedConventionsAttribute : AutoDataAttribute 
{ 
    public UnorderedConventionsAttribute() 
     : base(new Fixture().Customize(new UnorderedSyncItems())) 
    { 
    } 
} 

Questa è semplicemente la colla dichiarativa per applicare la personalizzazione UnorderedSyncItems. Il test diventa ora:

[Theory, UnorderedConventions] 
public void ConventionBasedTest(
    [Frozen]SyncItem[] unorderedItems, 
    SyncItemList sut) 
{ 
    var expected = unorderedItems.OrderBy(si => si.Key); 
    Assert.Equal(expected, sut); 
} 

Si noti l'uso degli attributi [UnorderedSyncItems] e [Frozen].

Questo test è molto sintetico, ma potrebbe non essere quello che cerchi. Il problema è che il cambiamento di comportamento è ora nascosto nell'attributo [UnorderedSyncItems], quindi è piuttosto implicito ciò che sta accadendo. Preferisco utilizzare la stessa personalizzazione come un insieme di convenzioni per un'intera suite di test, quindi non mi piace introdurre variazioni del caso di test a questo livello. Tuttavia, se le tue convenzioni stabiliscono che le istanze SyncItem[] devono essere sempre senza, questa convenzione è buona.

Tuttavia, se si desidera utilizzare solo array non ordinati per alcuni casi di test, l'utilizzo di un attributo [AutoData] non è l'approccio ottimale.

banco di prova dichiarativa

Sarebbe bello se si potrebbe semplicemente applicare un attributo a livello di parametri, proprio come l'attributo [Frozen] - forse la loro combinazione, come [Unordered][Frozen]. Tuttavia, questo approccio non funziona.

Nota dagli esempi precedenti che l'ordine è valido. È necessario applicare UnorderedSyncItems prima del blocco, in caso contrario, la matrice che si sta bloccando potrebbe non essere garantita non ordinata.

Il problema con [Unordered][Frozen] parametro a livello di attributi è che, mentre si compila, il framework .NET non garantisce l'ordine degli attributi quando la libreria colla AutoFixture xUnit.net legge e applica gli attributi.

Invece, è possibile definire un singolo attributo di applicare, in questo modo:

public class UnorderedFrozenAttribute : CustomizeAttribute 
{ 
    public override ICustomization GetCustomization(ParameterInfo parameter) 
    { 
     return new CompositeCustomization(    
      new UnorderedSyncItems(), 
      new FreezingCustomization(parameter.ParameterType)); 
    } 
} 

(FreezingCustomization prevede l'implementazione sottostante dell'attributo [Frozen].)

Ciò consente di scrivere questo test:

[Theory, AutoData] 
public void DeclarativeTest(
    [UnorderedFrozen]SyncItem[] unorderedItems, 
    SyncItemList sut) 
{ 
    var expected = unorderedItems.OrderBy(si => si.Key); 
    Assert.Equal(expected, sut); 
} 

Si noti che questo test dichiarativo utilizza l'attributo [AutoData] predefinito senza alcuna personalizzazione zazioni, poiché lo scrambling viene ora applicato dall'attributo [UnorderedFrozen] a livello di parametro.

Ciò consentirebbe anche di utilizzare una serie di (altre) convenzioni della suite di test incapsulate in un attributo -derived [AutoData] e utilizzare ancora [UnorderedFrozen] come meccanismo di attivazione.

+2

Wow, Mark, apprezzo molto l'investimento in questa risposta. La progressione è davvero preziosa per me nel capire gli strati di autofixture e come posso iniziare a riprenderli per ottenere veramente ciò che voglio in ogni tipo di scenario con cui ho a che fare. Complimenti e grazie! – cocogorilla