Domanda

So come uso questi termini, ma mi chiedo se ci sono definizioni accettate per fingendo , beffardo e stubbing 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 "buono" o "cattivo" dati a seconda dell'implementazione.

Mock : una classe che implementa un'interfaccia e consente di impostare dinamicamente i valori da restituire / eccezioni da generare da determinati metodi e offre la possibilità di verificare se determinati metodi sono stati chiamati / no chiamato.

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 principalmente le classi fasulle per sottrarre dipendenze ai dati o quando i mock / stub sono troppo noiosi da impostare ogni volta.

È stato utile?

Soluzione

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

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, ad esempio uno stub gateway e-mail che ricorda i messaggi che ha "inviato" o forse solo quanti messaggi ha "inviato".

Mock 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 delle stesse funzionalità fornite 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 è la presenza di codice non testato causato dall'impossibilità 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 eseguire 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.

Altri suggerimenti

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

Derisione - un oggetto su cui poni aspettative.

Fake : un oggetto con capacità limitate (ai fini del test), ad es. un servizio web falso.

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

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

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

  

Uno stub è una sostituzione controllabile per una dipendenza esistente   (o collaboratore) nel sistema. Usando uno stub, puoi testare il tuo codice senza   gestire direttamente la 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.

Fake è 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 utilizzata 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'istanza fake viene utilizzata come stub perché Assert non utilizza affatto fake .

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

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

cut.SquareIt;

Assert.AreEqual(25, fake.SomeProperty);

In questo caso il Assert controlla un valore su fake , rendendo il falso un falso.

Ora, ovviamente, 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's

  

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 sto sempre imparando 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 ActualClassUnderTest possono iniziare a rompersi perché l'implementazione per ClassUsedAsMock è cambiata. E questo mi fa venire un cattivo odore. I test per ActualClassUnderTest dovrebbero preferibilmente interrompersi solo quando ActualClassUnderTest viene modificato.

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

Per una lettura divertente del motivo per cui dovresti evitare le beffe, come definito qui, google per il "classicista beffardo fowler". Troverai moltissime opinioni.

Come menzionato dalla risposta più votata, Martin Fowler discute queste distinzioni in Mock Aren't Stubs , in particolare la sottovoce The Difference Between Mock and Stubs , quindi assicurati di leggere quell'articolo.

Piuttosto che concentrarsi su come 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 sfocati 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. utilizzo di sqlite con l'archivio : memory: ). 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 "reale". banca dati.

Come altro esempio, forse usi un qualche tipo di archivio oggetti (ad esempio Amazon S3) in produzione, ma in un test puoi semplicemente salvare oggetti su file su disco; quindi il tuo " salva su disco " l'implementazione sarebbe un falso. (Oppure potresti persino falsificare l'operazione "salva su disco" utilizzando 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 "innaturalmente". È preconfigurato (di solito dall'impostazione del test) per rispondere a input specifici con output specifici.

Lo scopo di uno stub è di mettere il sistema sotto test in uno stato specifico. Ad esempio, se stai scrivendo un test per un codice che interagisce con un'API REST, potresti 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.

Di solito uno stub è 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 tuo test case.

Mocks

Un finto è simile a uno stub, ma con verifica aggiunto. Lo scopo di un finto è fare affermazioni sul modo in cui il sistema sotto test ha interagito con la dipendenza .

Ad esempio, se stai scrivendo un test per un sistema che carica file su un sito Web, potresti creare un mock che accetta un file e che puoi usare per affermare che il file caricato era corretta. 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.

I mock sono legati al test di interazione , che è una metodologia di test specifica. Le persone che preferiscono testare stato del sistema piuttosto che interazioni di sistema useranno con parsimonia, se non del tutto.

Test raddoppia

I falsi, gli stub e le beffe appartengono tutti alla categoria dei test doppi . Un doppio di prova è qualsiasi oggetto o sistema y

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

Immagina di avere 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 una dipendenza esterna diversa, inviandolo via e-mail all'amministratore del servizio Web.

Ecco la logica che ci piacerebbe 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 che possiamo testare la chiamata al servizio di posta elettronica?

  • Come sapremo che il servizio di posta elettronica è stato chiamato correttamente o su tutti?

Siamo in grado di gestire le prime due domande utilizzando uno stub per il servizio web . Per risolvere il terzo problema, possiamo utilizzare 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 useremo il falso del servizio web per verificare il risultato del test, solo per assicurarci che il test venga eseguito correttamente. Il servizio di posta elettronica è un falso perché noi lo affermeremo contro 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);
 }
}

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.

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

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 alla sezione assert in quanto lo sono per l'affermazione dei 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 lingue rigorosamente tipizzate, in modo che tu possa essere autorizzato a compilare ed eseguire.

stub e fake sono oggetti in quanto possono variare la loro risposta in base a 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 rispetto a falsi e tronconi. 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ò tracciare 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;
 }

}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top