unit testing software Qual è la differenza tra fingere, deridere e stoppare?




unit test vs integration test (7)

So come uso questi termini, ma mi chiedo se ci sono definizioni accettate per simulare , beffare e stubare per i test unitari? Come li definisci per i tuoi test? Descrivi situazioni in cui potresti usarle ciascuna.

Ecco come li uso:

Falso : una classe che implementa un'interfaccia ma contiene dati fissi e nessuna logica. Restituisce semplicemente dati "buoni" o "cattivi" a seconda dell'implementazione.

Mock : una classe che implementa un'interfaccia e consente di impostare dinamicamente i valori da restituire / eccezioni per il lancio di particolari metodi e fornisce la possibilità di verificare se determinati metodi sono stati chiamati / non richiamati.

Stub : come una classe di simulazione, tranne per il fatto che non fornisce la possibilità di verificare che i metodi siano stati chiamati / non chiamati.

I mock e gli stub possono essere generati a mano o generati da una struttura di derisione. Le classi false sono generate a mano. Io uso principalmente i mock per verificare le interazioni tra la mia classe e le classi dipendenti. Io uso gli stub una volta che ho verificato le interazioni e sto testando percorsi alternativi attraverso il mio codice. Uso le classi fasulle principalmente per astrarre le dipendenze dei dati o quando i mock / stub sono troppo noiosi da configurare ogni volta.


Se hai familiarità con Arrange-Act-Assert, allora un modo per spiegare la differenza tra stub e mock che potrebbero essere utili per te, è che gli stub appartengono alla sezione arrangiamento, così come lo sono per organizzare lo stato di input, e i mock appartengono a la sezione di affermazione così come sono per affermare i risultati contro.

I manichini non fanno nulla. Sono solo per riempire gli elenchi di parametri, in modo da non ottenere errori non definiti o nulli. Esistono anche per soddisfare il correttore di tipi in lingue tipizzate rigorosamente, in modo da poter essere autorizzati a compilare ed eseguire.


Sono sorpreso che questa domanda sia in circolazione da così tanto tempo e nessuno abbia ancora fornito una risposta basata su "The Art of Unit Testing" di Roy Osherove .

In "3.1 Introdurre gli stub" definisce uno stub come:

Uno stub è una sostituzione controllabile per una dipendenza esistente (o un collaboratore) nel sistema. Utilizzando uno stub, puoi testare il tuo codice senza occuparti direttamente della dipendenza.

E definisce la differenza tra stub e mock come:

La cosa principale da ricordare su mock contro stub è che i mock sono come mozziconi, ma si asserisce contro l'oggetto finto, mentre non si asserisce contro uno stub.

Falso è solo il nome usato per entrambi gli stub e i mock. Ad esempio quando non ti interessa la distinzione tra matrici e mock.

Il modo in cui Osherove distingue tra matrici e mock, significa che qualsiasi classe usata come falso per i test può essere sia un stub che un mock. Quale è per un test specifico dipende interamente da come scrivi i controlli nel tuo test.

  • Quando il tuo test verifica i valori nella classe sotto test, o in realtà ovunque tranne il falso, il falso è stato usato come uno stub. Forniva solo i valori per la classe in prova da utilizzare, direttamente tramite i valori restituiti dalle chiamate su di esso o indirettamente causando effetti collaterali (in alcuni stati) come risultato delle chiamate su di essa.
  • Quando il tuo test verifica i valori del falso, è stato usato come un mock.

Esempio di un test in cui viene utilizzata la classe FakeX come stub:

const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);

cut.SquareIt;

Assert.AreEqual(25, cut.SomeProperty);

L'istanza fake è usata come uno stub perché l' Assert non usa affatto il fake .

Esempio di un test in cui la classe di test X è utilizzata come simulazione:

const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);

cut.SquareIt;

Assert.AreEqual(25, fake.SomeProperty);

In questo caso l' Assert controlla un valore su fake , rendendo falso un finto.

Ora, naturalmente, questi esempi sono molto elaborati, ma io vedo un grande merito in questa distinzione. Ti rende consapevole di come stai testando le tue cose e dove sono le dipendenze del test.

Sono d'accordo con Osherove

da una pura prospettiva di manutenibilità, nei miei test l'utilizzo di mock crea più problemi che non usarli. Questa è stata la mia esperienza, ma sto imparando sempre qualcosa di nuovo.

Assertare contro il falso è qualcosa che si vuole davvero evitare in quanto rende i test altamente dipendenti dall'implementazione di una classe che non è affatto sotto test. Il che significa che i test per la classe ActualClassUnderTest possono iniziare a rompersi perché l'implementazione di ClassUsedAsMock modificata. E questo manda un cattivo odore a me. I test per ActualClassUnderTest dovrebbero preferibilmente interrompersi solo quando ActualClassUnderTest viene modificato.

Mi rendo conto che la scrittura afferma contro il falso è una pratica comune, soprattutto quando sei un tipo mockista di abbonato TDD. Immagino di essere fermamente con Martin Fowler nel campo classicista (vedi "I mazzi non sono stub" di Martin Fowler ) e come Osherove evitare il test di interazione (che può essere fatto solo affermando contro il falso) il più possibile.

Per divertimento leggendo sul perché dovresti evitare i mock come definiti qui, google per "fowler mockist classicista". Troverete una pletora di opinioni.


Stub : un oggetto che fornisce risposte predefinite alle chiamate di metodo.

Mock : un oggetto su cui si impostano le aspettative.

Falso - un oggetto con funzionalità limitate (ai fini del test), ad esempio un falso servizio web.

Test Double è il termine generale per tronconi, mock e falsi. Ma in modo informale, sentirai spesso le persone chiamarle semplicemente mocciose.


la cosa che asserisci su di essa, si chiama un oggetto finto e tutto ciò che ha appena aiutato il test, è uno stub .


stub e falso sono oggetti in quanto possono variare la loro risposta in base ai parametri di input. la principale differenza tra loro è che un falso è più vicino a un'implementazione del mondo reale di uno stub. Gli stub contengono risposte fondamentalmente codificate a una richiesta prevista. Vediamo un esempio:

 public class MyUnitTest { @Test public void testConcatenate() { StubDependency stubDependency = new StubDependency(); int result = stubDependency.toNumber("one", "two"); assertEquals("onetwo", result); } } public class StubDependency() { public int toNumber(string param) { if (param == “one”) { return 1; } if (param == “two”) { return 2; } } } 

Un finto è un passo avanti da falsi e matrici. I mazzi forniscono le stesse funzionalità degli stub ma sono più complessi. Possono avere regole definite per loro che dettano in quale ordine devono essere chiamati i metodi sulla loro API. La maggior parte dei mock è in grado di tracciare quante volte è stato chiamato un metodo e può reagire in base a tali informazioni. I mouses generalmente conoscono il contesto di ogni chiamata e possono reagire in modo diverso nelle diverse situazioni. Per questo motivo, i mock richiedono una certa conoscenza della classe che stanno deridendo. in genere uno stub non può tracciare quante volte è stato chiamato un metodo o in quale ordine è stata chiamata una sequenza di metodi. Una finta assomiglia a:

 public class MockADependency { private int ShouldCallTwice; private boolean ShouldCallAtEnd; private boolean ShouldCallFirst; public int StringToInteger(String s) { if (s == "abc") { return 1; } if (s == "xyz") { return 2; } return 0; } public void ShouldCallFirst() { if ((ShouldCallTwice > 0) || ShouldCallAtEnd) throw new AssertionException("ShouldCallFirst not first thod called"); ShouldCallFirst = true; } public int ShouldCallTwice(string s) { if (!ShouldCallFirst) throw new AssertionException("ShouldCallTwice called before ShouldCallFirst"); if (ShouldCallAtEnd) throw new AssertionException("ShouldCallTwice called after ShouldCallAtEnd"); if (ShouldCallTwice >= 2) throw new AssertionException("ShouldCallTwice called more than twice"); ShouldCallTwice++; return StringToInteger(s); } public void ShouldCallAtEnd() { if (!ShouldCallFirst) throw new AssertionException("ShouldCallAtEnd called before ShouldCallFirst"); if (ShouldCallTwice != 2) throw new AssertionException("ShouldCallTwice not called twice"); ShouldCallAtEnd = true; } } 

Si tratta di rendere i test espressivi. Stabilisco delle aspettative su un Mock se voglio che il test descriva una relazione tra due oggetti. Ho stub valori di ritorno se sto impostando un oggetto di supporto per farmi il comportamento interessante nel test.


Per illustrare l'uso di stub e mock, vorrei anche includere un esempio basato su " The Art of Unit Testing " di Roy Osherove.

Immagina, abbiamo un'applicazione LogAnalyzer che ha l'unica funzionalità dei log di stampa. Non è solo necessario parlare con un servizio Web, ma se il servizio Web genera un errore, LogAnalyzer deve registrare l'errore in una dipendenza esterna diversa, inviandolo via email all'amministratore del servizio web.

Ecco la logica che vorremmo testare all'interno di LogAnalyzer:

if(fileName.Length<8)
{
 try
  {
    service.LogError("Filename too short:" + fileName);
  }
 catch (Exception e)
  {
    email.SendEmail("a","subject",e.Message);
  }
}

Come si verifica che LogAnalyzer chiami correttamente il servizio di posta elettronica quando il servizio Web genera un'eccezione? Ecco le domande che ci troviamo di fronte:

  • Come possiamo sostituire il servizio web?

  • Come possiamo simulare un'eccezione dal servizio Web in modo da poter testare la chiamata al servizio di posta elettronica?

  • Come sapremo che il servizio di posta elettronica è stato chiamato correttamente o del tutto?

Possiamo gestire le prime due domande utilizzando uno stub per il servizio web . Per risolvere il terzo problema, possiamo usare un oggetto fittizio per il servizio di posta elettronica .

Un falso è un termine generico che può essere usato per descrivere uno stub o una finta. Nel nostro test, avremo due falsi. Uno sarà il mock del servizio di posta elettronica, che utilizzeremo per verificare che i parametri corretti siano stati inviati al servizio di posta elettronica. L'altro sarà uno stub che useremo per simulare un'eccezione generata dal servizio web. È uno stub perché non useremo il servizio Web falso per verificare il risultato del test, ma solo per assicurarci che il test funzioni correttamente. Il servizio di posta elettronica è un finto perché affermiamo contro di esso che è stato chiamato correttamente.

[TestFixture]
public class LogAnalyzer2Tests
{
[Test]
 public void Analyze_WebServiceThrows_SendsEmail()
 {
   StubService stubService = new StubService();
   stubService.ToThrow= new Exception("fake exception");
   MockEmailService mockEmail = new MockEmailService();

   LogAnalyzer2 log = new LogAnalyzer2();
   log.Service = stubService
   log.Email=mockEmail;
   string tooShortFileName="abc.ext";
   log.Analyze(tooShortFileName);

   Assert.AreEqual("a",mockEmail.To); //MOCKING USED
   Assert.AreEqual("fake exception",mockEmail.Body); //MOCKING USED
   Assert.AreEqual("subject",mockEmail.Subject);

 }
}




stub