Qual è la differenza tra falsificazione, derisione e stub?


706

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

Ecco come li uso:

Fake : una classe che implementa un'interfaccia ma contiene dati fissi e nessuna logica. Restituisce semplicemente i 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 da lanciare da determinati metodi e offre la possibilità di verificare se determinati metodi sono stati chiamati / non chiamati.

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

Mock e stub possono essere generati a mano o generati da un framework beffardo. Le classi false sono generate a mano. Uso principalmente le beffe per verificare le interazioni tra la mia classe e le classi dipendenti. Uso gli stub dopo aver verificato le interazioni e sto testando percorsi alternativi attraverso il mio codice. Uso le classi fasulle principalmente per sottrarre dipendenze ai dati o quando mock / stub sono troppo noiosi da impostare ogni volta.


6
Beh, in pratica hai detto tutto nella tua "domanda" :) Penso che quelle siano definizioni abbastanza ben accettate di quei termini
Eran Galperin,

2
La definizione di Fake di Wikipedia differisce da questa, affermando che un Fake "viene utilizzato come un'implementazione più semplice, ad esempio utilizzando un database in memoria nei test invece di fare un vero accesso al database)" Vedi en.wikipedia.org/wiki/Test_double
zumalifeguard,

2
Ho imparato molto dalla seguente risorsa, con un'eccellente spiegazione di Robert C. Martin (Zio Bob): The Little Mocker su The Clean Code Blog . Spiega le differenze tra e le sottigliezze dei manichini, i doppi dei test, i tronconi, le spie, i falsi (veri) falsi. Menziona anche Martin Fowler e spiega un po 'di storia dei test del software.
Erik,

testing.googleblog.com/2013/07/… (un breve riepilogo di una pagina).
ShreevatsaR,

Ecco la mia opinione per spiegarlo: Test Doubles: Fakes, Stubs and Mocks (post sul blog con esempi)
michal-lipski

Risposte:


548

Puoi ottenere alcune informazioni:

Da Martin Fowler su Mock and Stub

Gli oggetti falsi in realtà hanno implementazioni funzionanti, ma di solito prendono qualche scorciatoia che li rende non adatti alla produzione

Gli stub forniscono risposte predefinite alle chiamate effettuate durante il test, di solito non rispondono a nulla al di fuori di ciò che è programmato per il test. Gli stub possono anche registrare informazioni sulle chiamate, come uno stub del gateway di posta elettronica che ricorda i messaggi che ha "inviato" o forse solo quanti messaggi ha "inviato".

Le finte sono ciò di cui stiamo parlando qui: oggetti pre-programmati con aspettative che formano una specifica delle chiamate che dovrebbero ricevere.

Da xunitpattern :

Falso : acquisiamo o costruiamo un'implementazione molto leggera della stessa funzionalità fornita da un componente da cui dipende il SUT e chiediamo al SUT di usarlo al posto del reale.

Stub : questa implementazione è configurata per rispondere alle chiamate dal SUT con i valori (o le eccezioni) che eserciteranno il codice non testato (vedi Bug di produzione a pagina X) all'interno del SUT. Un'indicazione chiave per l'utilizzo di uno stub di test è avere codice non testato causato dall'incapacità di controllare gli input indiretti del SUT

Mock Object che implementa la stessa interfaccia di un oggetto da cui dipende il SUT (System Under Test). Possiamo usare un oggetto simulato come punto di osservazione quando dobbiamo fare la verifica del comportamento per evitare di avere un requisito non testato (vedi Bug di produzione a pagina X) causato dall'incapacità di osservare gli effetti collaterali dei metodi di invocazione sul SUT.

Personalmente

Cerco di semplificare usando: Mock and Stub. Uso Mock quando è un oggetto che restituisce un valore impostato sulla classe testata. Uso Stub per imitare un'interfaccia o una classe astratta da testare. In realtà, non importa come lo chiami, sono tutte classi che non vengono utilizzate nella produzione e vengono utilizzate come classi di utilità per i test.


9
Mi sembra che le definizioni di Stub e Fake siano invertite nella citazione xUnitPattern rispetto alla citazione di Martin Fowler. Inoltre, le definizioni di Martin Fowler di Stub e Fake sono invertite rispetto alle definizioni della domanda originale di tvanfosson. In realtà esiste qualche definizione generalmente accettata di quei due termini o dipende solo da chi stai parlando?
Simon Tewsi,

3
+1 per "Cerco di semplificare usando: Mock and Stub". Questa è una grande idea!
Brad Cupit,

4
Non riesco a vedere come usare solo Mock and Stub sia un'ottima idea. Ogni test doppio ha i suoi scopi e, quindi, i suoi usi.
Hector Ordonez,

1
Non riesco a vedere la differenza tra Fake e Mock nella definizione di MF.
IdontCareAboutReputationPoints

2
@MusuNaji: Nella definizione di MF non ci sono "aspettative" riguardo alla conversazione per un falso, a parte il fatto che ha un'implementazione per la sua interfaccia. D'altra parte il Mock sarà sfidato (questo metodo è stato chiamato?).
dbalakirev,

205

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

Mock - un oggetto su cui poni aspettative.

Falso : un oggetto con capacità limitate (ai fini del test), ad esempio un servizio Web falso.

Test Double è il termine generale per tronconi, imitazioni e falsi. Ma informalmente, sentirai spesso le persone semplicemente chiamarle derisioni.


4
Qualcuno potrebbe spiegare e definire per me che cosa è una "risposta in scatola" in questo contesto?
MasterMastic

14
Un valore esplicito, anziché un valore calcolato.
Mike,

Finalmente! Alcune definizioni che posso capire! Sulla base di queste definizioni, quindi, googletest (gtest) / googlemock (gmock) consente agli oggetti derisi di essere anche stub, in quanto è possibile creare EXPECT_CALL()s su un metodo deriso che forza determinati output in base a determinati input, utilizzando il tipo .WillOnce(Invoke(my_func_or_lambda_func))(o con .WillRepeatedly()) sintassi allegata a un EXPECT_CALL(). Alcuni esempi di utilizzo Invoke()possono essere visti in un contesto diverso in fondo alla mia lunga risposta qui: stackoverflow.com/a/60905880/4561887 .
Gabriel Staples,

La documentazione di Gmock su Invoke()è qui: github.com/google/googletest/blob/master/googlemock/docs/… . Ad ogni modo, la conclusione è: Google mock (gmock) consente di creare facilmente sia beffe che tronconi , sebbene la maggior parte delle derisioni non siano tronconi.
Gabriel Staples,

I mock sono un superset di Stub, possono ancora restituire risposte predefinite, ma consentono anche allo sviluppatore di stabilire aspettative. Alcune biblioteche IMO là fuori sfocano le linee di tutti i manichini di prova.
Luca,

94

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

In "3.1 Introduzione agli stub" si definisce uno stub come:

Uno stub è un sostituto controllabile per una dipendenza esistente (o collaboratore) nel sistema. Utilizzando uno stub, è possibile testare il codice senza occuparsi direttamente della dipendenza.

E definisce la differenza tra stub e mock come:

La cosa principale da ricordare sulle beffe contro gli stub è che le beffe sono proprio come gli stub, ma affermi contro l'oggetto simulato, mentre non affermi contro uno stub.

Falso è solo il nome usato sia per gli stub che per i mock. Ad esempio quando non ti interessa la distinzione tra mozziconi e beffe.

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

  • Quando il tuo test controlla i valori nella classe sotto test, o in realtà ovunque tranne il falso, il falso è stato usato come stub. Forniva solo i valori che la classe sotto test poteva usare, direttamente attraverso i valori restituiti dalle chiamate su di essa o indirettamente causando effetti collaterali (in alcuni stati) come risultato delle chiamate su di essa.
  • Quando il tuo test controlla i valori del falso, è stato usato come un finto.

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

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

cut.SquareIt;

Assert.AreEqual(25, cut.SomeProperty);

L' fakeistanza viene utilizzata come stub perché Assertnon viene utilizzata fakeaffatto.

Esempio di un test in cui la classe di test X viene utilizzata come finto:

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

cut.SquareIt;

Assert.AreEqual(25, fake.SomeProperty);

In questo caso il Assertcontrollo ha un valore fake, rendendo quel falso un gioco d'azzardo.

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

Sono d'accordo con Osherove

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

Affermare contro il falso è qualcosa che vuoi davvero evitare in quanto rende i tuoi test altamente dipendenti dall'implementazione di una classe che non è affatto quella sotto test. Ciò significa che i test per la classe ActualClassUnderTestpossono iniziare a fallire perché l'implementazione per è ClassUsedAsMockcambiata. E questo mi fa venire un cattivo odore. I test per ActualClassUnderTestdovrebbero preferibilmente interrompersi solo quando ActualClassUnderTestviene modificato.

Mi rendo conto che scrivere asserzioni contro il falso è una pratica comune, specialmente quando sei un tipo beffardo di abbonati TDD. Immagino di stare fermamente con Martin Fowler nel campo classicista (vedi "Mock not Stubs" di Martin Fowler ) e come Osherove evito il più possibile i test di interazione (che possono essere fatti solo affermando contro il falso).

Per una lettura divertente del perché dovresti evitare le beffe come definito qui, google per "fowler mockist classicista". Troverai moltissime opinioni.


30

Come menzionato dalla risposta più votata, Martin Fowler discute queste distinzioni in Mocks Aren't Stubs , e in particolare nel sottotitolo The Difference Between Mock and Stubs , quindi assicurati di leggere l'articolo.

Invece di concentrarmi sul modo in cui queste cose sono diverse, penso che sia più illuminante concentrarsi sul perché questi sono concetti distinti. Ognuno esiste per uno scopo diverso.

falsi

Un falso è un'implementazione che si comporta "naturalmente", ma non è "reale". Questi sono concetti confusi e quindi persone diverse hanno una comprensione diversa di ciò che rende le cose un falso.

Un esempio di falso è un database in memoria (ad es. Usando sqlite con l' :memory:archivio). Non lo useresti mai per la produzione (poiché i dati non sono persistenti), ma è perfettamente adeguato come database da utilizzare in un ambiente di test. È anche molto più leggero di un database "reale".

Come altro esempio, forse usi un qualche tipo di archivio oggetti (es. Amazon S3) in produzione, ma in un test puoi semplicemente salvare oggetti su file su disco; quindi l'implementazione "salva su disco" sarebbe un falso. (O potresti persino falsificare l'operazione "salva su disco" utilizzando invece un filesystem in memoria.)

Come terzo esempio, immagina un oggetto che fornisce un'API cache; un oggetto che implementa l'interfaccia corretta ma che semplicemente non esegue affatto la memorizzazione nella cache ma restituisce sempre un errore nella cache sarebbe una specie di falso.

Lo scopo di un falso non è influenzare il comportamento del sistema in prova , ma piuttosto semplificare l'implementazione del test (rimuovendo dipendenze non necessarie o pesanti).

stubs

Uno stub è un'implementazione che si comporta "in modo innaturale". È preconfigurato (solitamente dall'impostazione del test) per rispondere a ingressi specifici con uscite specifiche.

Lo scopo di uno stub è di mettere il sistema sotto test in uno stato specifico. Ad esempio, se si sta scrivendo un test per un codice che interagisce con un'API REST, è possibile stub l'API REST con un'API che restituisce sempre una risposta predefinita o che risponde a una richiesta API con un errore specifico. In questo modo è possibile scrivere test che fanno affermazioni su come il sistema reagisce a questi stati; ad esempio, testando la risposta che ottengono gli utenti se l'API restituisce un errore 404.

Uno stub di solito è implementato per rispondere solo alle interazioni esatte a cui gli hai detto di rispondere. Ma la caratteristica chiave che rende qualcosa uno stub è il suo scopo : uno stub è tutto sulla configurazione del test case.

Mocks

Un mock è simile a un troncone, ma con la verifica aggiunta. Lo scopo di un mock è fare affermazioni su come il sistema in test ha interagito con la dipendenza .

Ad esempio, se si sta scrivendo un test per un sistema che carica file su un sito Web, è possibile creare una simulazione che accetta un file e che è possibile utilizzare per affermare che il file caricato era corretto. Oppure, su scala ridotta, è comune utilizzare una simulazione di un oggetto per verificare che il sistema sottoposto a test chiami metodi specifici dell'oggetto deriso.

Le simulazioni sono legate ai test di interazione , che è una metodologia di test specifica. Le persone che preferiscono testare lo stato del sistema piuttosto che le interazioni del sistema useranno con parsimonia se non del tutto.

Il test raddoppia

Falsi, tronconi e beffe appartengono tutti alla categoria dei doppi di test . Un doppio test è qualsiasi oggetto o sistema che usi in un test invece di qualcos'altro. La maggior parte dei test del software automatizzato prevede l'uso di duplicati di test di un tipo o di un altro. Alcuni altri tipi di test raddoppia includono valori fittizi , spie , e di I / O blackholes .


11

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 la sola funzionalità di stampare registri. Non è necessario solo parlare con un servizio Web, ma se il servizio Web genera un errore, LogAnalyzer deve registrare l'errore in un'altra dipendenza esterna, inviandolo via e-mail 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 dobbiamo affrontare:

  • 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 per niente?

Siamo in grado di affrontare le prime due domande utilizzando uno stub per il servizio Web . Per risolvere il terzo problema, possiamo usare un oggetto simulato per il servizio di posta elettronica .

Un falso è un termine generico che può essere usato per descrivere uno stub o un finto. Nel nostro test avremo due falsi. Uno sarà il finto servizio di posta elettronica, che useremo 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 utilizzeremo il servizio Web fake per verificare il risultato del test, solo per assicurarci che il test venga eseguito correttamente. Il servizio di posta elettronica è un falso perché faremo valere 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);
 }
}

9

la cosa che affermi su di esso, si chiama un oggetto finto e tutto il resto che ha appena aiutato l'esecuzione del test, è un troncone .


1
mentre altre risposte hanno un grande dettaglio e sono davvero buone. questo rende così chiaro e facile fare la differenza, è difficile non votare. gj!
Mario Garcia,

6

Si tratta di rendere espressivi i test. Metto le aspettative su un Mock se voglio che il test descriva una relazione tra due oggetti. Stub restituisco i valori se sto impostando un oggetto di supporto per portarmi al comportamento interessante nel test.


6

Se hai familiarità con Arrange-Act-Assert, un modo per spiegare la differenza tra stub e mock che potrebbe esserti utile, è che gli stub appartengono alla sezione organizza, così come sono per organizzare lo stato di input, e i mock appartengono a la sezione assert 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 indefiniti o nulli. Esistono anche per soddisfare la verifica del tipo in linguaggi rigorosamente tipizzati, in modo da poter compilare ed eseguire.


3

Stub, Fakes and Mock hanno significati diversi in diverse fonti. Ti suggerisco di introdurre i termini interni del tuo team e concordare il loro significato.

Penso che sia importante distinguere tra due approcci: - validazione del comportamento (implica la sostituzione del comportamento) - validazione dello stato finale (implica l'emulazione del comportamento)

Prendi in considerazione l'invio di e-mail in caso di errore. Nel fare la convalida comportamento - di verificare che il metodo Senddi IEmailSenderè stato eseguito una sola volta. Ed è necessario emulare il risultato di ritorno di questo metodo, restituire l'ID del messaggio inviato. Quindi dici: "Mi aspetto che Sendverrà chiamato. E restituirò un ID fittizio (o casuale) per qualsiasi chiamata" . Questa è la convalida del comportamento: emailSender.Expect(es=>es.Send(anyThing)).Return((subject,body) => "dummyId")

Quando si esegue la convalida dello stato, è necessario creare TestEmailSendertali strumenti IEmailSender. E implementa il Sendmetodo: salvando l'input in una struttura di dati che verrà utilizzata per la verifica dello stato futuro come array di alcuni oggetti SentEmailse quindi verificherà che SentEmailscontenga la posta elettronica prevista. Questa è la convalida dello stato: Assert.AreEqual(1, emailSender.SentEmails.Count)

Dalle mie letture ho capito che la convalida del comportamento di solito si chiama Mock . E la convalida dello stato di solito chiamata Stub o Fakes .


Definizione davvero ben dettagliata e nitida.
shyam sundar singh tomar,

2

stub e fake sono oggetti in quanto possono variare la loro risposta in base ai parametri di input. la differenza principale tra loro è che un falso è più vicino a un'implementazione nel mondo reale che a uno stub. Gli stub contengono risposte sostanzialmente 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 mozziconi. Le simulazioni offrono le stesse funzionalità degli stub ma sono più complesse. Possono avere regole definite per loro che dettano in quale ordine devono essere chiamati i metodi nell'API. La maggior parte delle beffe può tenere traccia di quante volte è stato chiamato un metodo e può reagire in base a tali informazioni. Le simulazioni generalmente conoscono il contesto di ogni chiamata e possono reagire in modo diverso in situazioni diverse. Per questo motivo, le beffe richiedono una certa conoscenza della classe che stanno prendendo in giro. uno stub generalmente non è in grado di tracciare quante volte è stato chiamato un metodo o in quale ordine è stata chiamata una sequenza di metodi. Un finto 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;
 }

}

1

fake objectè una vera implementazione dell'interfaccia (protocollo) o un'estensione che usa l'ereditarietà o altri approcci che possono essere usati per creare - è dipendenza. Di solito viene creato dallo sviluppatore come una soluzione più semplice per sostituire alcune dipendenze

stub objectè un oggetto nudo (0, zero e metodi senza logica) con ed extra e predefiniti (dallo sviluppatore) Stato definire valori restituiti. Di solito è creato da framework

mock objectè molto simile stub objectma lo stato extra viene modificato durante l'esecuzione del programma per verificare se è successo qualcosa (è stato chiamato il metodo).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.