2014-06-23 14 views
12

scrivo di prova per questo è di classe gestire alberi di oggetti Tag:Testing Rimuovere metodo senza una chiamata al metodo Add

public class Tag 
{ 
    public virtual int Id { get; set; } 
    public virtual string Description{ get; set; } 
    private IList<Tag> children = new List<Tag>(); 
    public virtual IEnumerable<Tag> Children 
    { 
     get {return children .ToArray();} 
    } 
    public void AddChildTag(Tag child) 
    { 
     children.Add(child); 
    } 
    public void RemoveChildTag(Tag child) 
    { 
     children.Remove(child); 
    } 
} 

Come si può vedere l'unica modalità per impostare la proprietà genitore è attraverso il metodo AddChildTag e questo è esattamente quello che voglio, il mio problema è nel test delle unità: poiché ogni test dovrebbe essere atomico, come posso testare il metodo RemoveChildTag?

L'unico modo che vedo è una chiamata al metodo add e in seguito alla rimozione, ma in questo modo se Aggiungi come alcuni errori, anche il test di rimozione avrà esito negativo, quindi l'atomicità viene persa.

Come si può fare?

EDIT
ho tolto proprietà Parent dall'oggetto Tag, dal momento che non è più lo uso Alcuni test secondo soluzione utilizzando NUnit e FluentAssertion

[Test] 
    public void AddChildTagAddsChildren() 
    { 
     //Arrange 
     Tag parent = new Tag(); 
     Tag child = new Tag(); 
     //Act 
     parent.AddChildTag(child); 
     //Assert 
     parent.Children.Should().Contain(child); 
    } 
    [Test] 
    public void RemoveChildTagRemovesAddedChildren() 
    { 
     //Arrange 
     Tag parent = new Tag(); 
     Tag child = new Tag(); 
     parent.AddChildTag(child); 
     //Act 
     parent.RemoveChildTag(child); 
     //Assert 
     parent.Children.Should().NotContain(child); 
    } 
    [Test] 
    public void RemoveChildTagThrowsNothingWhenNoChild() 
    { 
     //Arrange 
     Tag parent= new Tag(); 
     Tag child= new Tag(); 
     //Act 
     Action RemoveChild =() => parent.RemoveChildTag(child); 
     //Assert 
     RemoveChild.ShouldNotThrow(); 
    } 
+1

Leggera nota a margine: vale anche la pena di testare il comportamento di chiamare 'Remove' senza chiamare' Add' per verificare che nessuna eccezione sia lanciata o che l'eccezione corretta sia generata con lo stato corretto. – Jonny

+0

Poiché la rimozione dipende da 'add', se il test passa si sa che' se aggiungi funziona, quindi rimuovi works'. Una volta che 'add' è stato testato, allora sai che' remove' funziona. – Cruncher

+0

@Jonny, correggere vedere il mio commento su Lukazoid risposta –

risposta

21

I test di unità dovrebbero riflettere effettivi casi di utilizzo della classe. In che modo i tuoi consumatori useranno il metodo RemoveChildTag? Quale ha più senso? Qual è il modo in cui useresti la raccolta di oggetti?

var parent = new Tag(); 
// later 
parent.RemoveChildTag(child); 

... o

var parent = new Tag(); 
parent.AddChildTag(child); 
// later 
parent.RemoveChildTag(child); 

vostri consumatori potranno rimuovere gli oggetti che precedentemente aggiunto. Questo è il tuo caso d'uso, "Rimuovi rimuove elementi precedentemente aggiunti" (nota che produce anche un eccellente nome del metodo di prova).

Add e Remove I metodi sono spesso complementari: non è possibile testarne uno senza l'altro.

+0

@ gt.guybrush Penso che solo questo può essere la risposta corretta, interrompere l'incapsulamento a scopo di test non è l'approccio corretto – InferOn

14

Ci sono alcuni modi per testare il metodo Remove:

  1. Mocking - simulare la struttura dei dati e chiamare rimuovere, in modo da poter testare chiamando i metodi giusti
  2. Ereditarietà - rendere protetto children. La classe di test erediterà dalla classe Tag. Ora è possibile init children membro in modo da poter testare la rimozione Metodo
  3. Usa Add

Credo che l'opzione 3 è il migliore, è ok utilizzando altri metodi nella tua unit test, se Add hanno qualche errore, più di 1 test fallirà - dovresti essere in grado di capire cosa devi correggere.

Inoltre, ogni test di unità dovrebbe testare alcuni comportamenti di base, anche se è necessario eseguire prima una preparazione. Se quelli di preparazione non è riuscita, falliscono il test con i commenti relativi

Opzione 1 - è migliore quando si sta raggiungendo il codice di 3a parte - altro server, file system & di più. Dal momento che non vuoi effettuare quelle chiamate in unit test - prendi in giro la risposta.

Opzione 2 - migliore che posso dire, è quando si desidera verificare Metodo protetto/privato, senza la necessità di effettuare tutte le chiamate "sulla strada" che il codice fa (metodo pubblico che fanno molte chiamate che alla fine chiama il metodo che vuoi testare, dato che vuoi testare solo una logica specifica. È anche facile usare questa opzione quando la tua classe ha uno stato che vuoi testare, senza dover scrivere molto codice per portare la tua classe in questo stato.

+2

+1 d'accordo, l'opzione 3 è la migliore – RvdK

+0

io contrassegno jimmy_keen naswer come soluzione solo per nominare un esempio di test del metodo. Se potessi segnare due risposte, segnerei anche il tuo (posso semplicemente aggiungere un +1) –

3

È possibile usare un PrivateObject Class per Disporre l'oggetto in prova

Consente codice di prova per chiamare metodi e proprietà sul codice in prova che sarebbero inaccessibili perché non sono pubbliche.

EDIT

allora si potrebbe ottenere pieno accesso a oggetto avvolto da PrivateObject.RealType e PrivateObject.Target Proprietà

EDIT

In ogni caso, tutti i sistemi che rompono la segregazione e l'incapsulamento di un classe rendere inutile l'approccio scatola nera di Unità Testing in TDD e dovrebbe essere evitato come un veleno :)

+0

Ciò può causare il comportamento dell'applicazione in un flusso diverso. In questo modo, child.parent non è impostato e potrebbe causare altri errori.(probabilmente non in questo caso, ma in altri potrebbe farlo) – RvdK

+0

@RvdK hai perfettamente ragione, comunque un PrivateObject offre la possibilità di fare ciò che è richiesto senza la necessità di complesse operazioni di simulazione mocking – InferOn

+0

lo provo: PrivateObject po = new PrivateObject (typeof (Tag)); po.SetProperty ("Parent", parent); ma ora come posso lanciare questo oggetto su un tag come parametro per chiamare il metodo remove? –

2

Un approccio comune al test delle unità è il paradigma Arrange-Act-Assert.

Avrei personalmente due test per il metodo di rimozione, uno che rimuove un bambino che non è mai stato aggiunto, cosa dovrebbe accadere? Dovrebbe essere lanciata un'eccezione?

Questo sarebbe simile:

[Test] 
public void RemoveChildTagThrowsExceptionWhenNoChildren() 
{ 
    // Arrange 
    var tag = new Tag(); 
    var tagToRemove = new Tag(); 

    // Act & Assert 
    Expect(() => tag.RemoveChildTag(tagToRemove), Throws.ArgumentException); 
} 

Poi avrei un altro test per quello che dovrebbe accadere quando un bambino aggiunto viene rimosso:

[Test] 
public void RemoveChildTagRemovesPreviouslyAddedChild() 
{ 
    // Arrange 
    var tag = new Tag(); 
    var childTag = new Tag(); 
    tag.AddChildTag(childTag); 

    // Act 
    tag.RemoveChildTag(childTag); 

    // Assert 
    Expect(tag.Children.Contains(childTag), Is.False); 
} 

Può essere interessante notare che un bel molte implementazioni .NET Remove restituiscono un risultato bool che indica se è stata effettivamente eseguita una rimozione. Vedi here.

+0

io uso una sintassi simile ma con Nunit e FluentAssertion (sintassi più leggibile: tag.Children.Should(). NotContain (childTag)). il ritorno di bool è interessante ma perché non l'aggiunta? Per il primo caso preferisco non lanciare un'eccezione, semplicemente non faccio nulla –

Problemi correlati