Qual è lo scopo degli oggetti finti?


167

Sono nuovo ai test unitari e continuo a sentire le parole "oggetti finti" lanciati molto intorno. In parole povere, qualcuno può spiegare cosa sono gli oggetti finti e a cosa servono in genere durante la scrittura di unit test?


12
Sono uno strumento per ingegnerizzare eccessivamente le cose con la flessibilità che non è necessaria per il problema.
dsimcha,

2
possibile duplicato di What is Mocking?
nawfal,

Risposte:


361

Dato che dici di essere nuovo ai test unitari e di aver chiesto oggetti finti in "termini da laici", proverò l'esempio di un laico.

Test unitari

Immagina test unitari per questo sistema:

cook <- waiter <- customer

In genere è facile prevedere di testare un componente di basso livello come cook:

cook <- test driver

Il collaudatore ordina semplicemente diversi piatti e verifica che il cuoco restituisca il piatto corretto per ciascun ordine.

È più difficile testare un componente intermedio, come il cameriere, che utilizza il comportamento di altri componenti. Un tester ingenuo potrebbe testare il componente cameriere allo stesso modo in cui abbiamo testato il componente cuoco:

cook <- waiter <- test driver

L'autista del test ordina piatti diversi e si assicura che il cameriere restituisca il piatto corretto. Sfortunatamente, ciò significa che questo test del componente cameriere può dipendere dal comportamento corretto del componente cuoco. Questa dipendenza è ancora peggiore se il componente cuoco ha caratteristiche ostili al test, come il comportamento non deterministico (il menu include la sorpresa dello chef come piatto), molte dipendenze (il cuoco non cucinerà senza tutto il suo staff) o molti risorse (alcuni piatti richiedono ingredienti costosi o impiegano un'ora per cucinare).

Dato che si tratta di un test per camerieri, idealmente, vogliamo testare solo il cameriere, non il cuoco. In particolare, vogliamo assicurarci che il cameriere trasmetta correttamente l'ordine del cliente al cuoco e consegni correttamente il cibo del cuoco al cliente.

Test unità significa testare le unità in modo indipendente, quindi un approccio migliore sarebbe quello di isolare il componente in prova (il cameriere) usando ciò che Fowler chiama test double (manichini, tronconi, falsi, beffe) .

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

Qui, il cuoco di prova è "in combutta" con il collaudatore. Idealmente, il sistema in prova è progettato in modo tale che il cuoco di prova possa essere facilmente sostituito ( iniettato ) per lavorare con il cameriere senza cambiare il codice di produzione (ad esempio senza cambiare il codice del cameriere).

Oggetti finti

Ora, il cuoco di prova (test doppio) potrebbe essere implementato in diversi modi:

  • un falso cuoco - qualcuno che finge di essere un cuoco usando cene congelate e un forno a microonde,
  • un cuoco stub - un fornitore di hot dog che ti dà sempre hot dog indipendentemente da ciò che ordini, o
  • un cuoco finto - un poliziotto sotto copertura che segue una sceneggiatura che finge di essere un cuoco in un'operazione pungente.

Vedi l'articolo di Fowler per maggiori dettagli su falsi vs tronconi vs derisioni contro manichini , ma per ora, concentriamoci su un cuoco finto.

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

Una gran parte dell'unità che verifica il componente cameriere si concentra su come il cameriere interagisce con il componente cuoco. Un approccio basato sul finto si concentra sulla specifica completa di quale sia l'interazione corretta e sul rilevamento di quando va storto.

L'oggetto finto sa in anticipo cosa dovrebbe accadere durante il test (ad es. Quale dei suoi metodi chiamerà le chiamate, ecc.) E l'oggetto finto sa come dovrebbe reagire (ad es. Quale valore restituito fornire). La simulazione indicherà se ciò che accade realmente differisce da ciò che dovrebbe accadere. Un oggetto fittizio personalizzato può essere creato da zero per ciascun caso di test per eseguire il comportamento previsto per quel caso di test, ma un framework di simulazione si sforza di consentire che una specifica di comportamento di questo tipo sia chiaramente e facilmente indicata direttamente nel caso di test.

La conversazione che circonda un test basato su simulazioni potrebbe apparire così:

collaudatore per deridere il cuoco : aspettarsi un ordine di hot dog e dargli questo manichino hot dog in risposta

test driver (in posa come cliente) al cameriere : vorrei un hot dog per favore
cameriere per deridere cuoco : 1 hot dog per favore
finto cuoco per cameriere : ordine: 1 hot dog pronto (dà un hot dog fittizio al cameriere)
cameriere per test driver : ecco il tuo hot dog (fornisce un hot dog fittizio al collaudatore)

test driver : TEST SUCCEEDED!

Ma poiché il nostro cameriere è nuovo, questo è ciò che potrebbe accadere:

collaudatore per deridere il cuoco : aspettarsi un ordine di hot dog e dargli questo manichino hot dog in risposta

test driver (in posa come cliente) al cameriere : vorrei un hot dog per favore
cameriere per deridere cuoco : 1 hamburger per favore
finto cuoco interrompe il test: mi è stato detto di aspettarmi un ordine di hot dog!

test driver rileva il problema: PROVA NON RIUSCITA! - il cameriere ha cambiato l'ordine

o

collaudatore per deridere il cuoco : aspettarsi un ordine di hot dog e dargli questo manichino hot dog in risposta

test driver (in posa come cliente) al cameriere : vorrei un hot dog per favore
cameriere per deridere cuoco : 1 hot dog per favore
finto cuoco per cameriere : ordine: 1 hot dog pronto (dà un hot dog fittizio al cameriere)
cameriere per test driver : ecco le tue patatine fritte (dà patatine fritte da un altro ordine per testare il conducente)

il collaudatore nota le patatine inaspettate: TEST NON RIUSCITO! il cameriere restituì il piatto sbagliato

Potrebbe essere difficile vedere chiaramente la differenza tra oggetti finti e tronconi senza un esempio contrastante basato su tronconi per procedere con questo, ma questa risposta è già troppo lunga :-)

Si noti inoltre che questo è un esempio piuttosto semplicistico e che i framework di derisione consentono alcune specifiche piuttosto sofisticate del comportamento previsto dai componenti per supportare test completi. C'è un sacco di materiale su oggetti finti e quadri beffardi per ulteriori informazioni.


12
Questa è un'ottima spiegazione, ma non stai testando in qualche modo l'implementazione del cameriere? Nel tuo caso, probabilmente va bene perché stai controllando che usi l'API corretta, ma cosa succede se ci sono diversi modi per farlo e il cameriere potrebbe scegliere l'uno o l'altro? Ho pensato che il punto del test unitario fosse testare l'API e non l'implementazione. (Questa è una domanda che mi trovo sempre a porsi quando leggo di derisione.)
davidtbernal

8
Grazie. Non posso dire se stiamo testando l '"implementazione" senza vedere (o definire) le specifiche per il cameriere. Puoi presumere che al cameriere sia permesso cucinare il piatto da solo o riempire l'ordine in fondo alla strada, ma presumo che le specifiche per il cameriere includano l'uso dello chef previsto - dopo tutto, lo chef di produzione è un cuoco gourmet costoso e noi ' preferirei che il nostro cameriere lo usasse. Senza quella specifica, suppongo che dovrei concludere che hai ragione: il cameriere può riempire l'ordine come vuole per essere "corretto". OTOH, senza una specifica, il test non ha senso. [Continua ...]
Bert F,

8
MAI, fai un grande punto che porta al fantastico argomento del test delle unità scatola bianca e scatola nera. Non credo che ci sia un consenso del settore in base al quale i test unitari devono essere black-box anziché white-box ("testare l'API, non l'implementazione"). Penso che probabilmente il miglior test unitario debba essere una combinazione dei due per bilanciare la fragilità del test con la copertura del codice e la completezza del test case.
Bert F

1
Questa risposta secondo me non è abbastanza tecnica. Voglio sapere perché dovrei usare un oggetto finto quando posso usare oggetti reali.
Niklas R.,

1
Ottima spiegazione !! Grazie!! @BertF
Bharath Murali

28

Un oggetto simulato è un oggetto che sostituisce un oggetto reale. Nella programmazione orientata agli oggetti, gli oggetti simulati sono oggetti simulati che imitano il comportamento di oggetti reali in modo controllato.

Un programmatore di computer in genere crea un oggetto simulato per testare il comportamento di qualche altro oggetto, più o meno allo stesso modo in cui un progettista di auto utilizza un manichino da crash test per simulare il comportamento dinamico di un essere umano negli impatti del veicolo.

http://en.wikipedia.org/wiki/Mock_object

Gli oggetti simulati consentono di impostare scenari di test senza far fronte a risorse ingombranti e ingombranti come i database. Invece di chiamare un database per il test, è possibile simulare il database utilizzando un oggetto finto nei test delle unità. Questo ti libera dall'onere di dover impostare e demolire un vero database, solo per testare un singolo metodo nella tua classe.

La parola "Mock" è talvolta erroneamente usata in modo intercambiabile con "Stub". Le differenze tra le due parole sono descritte qui. In sostanza, un finto è un oggetto stub che include anche le aspettative (cioè "asserzioni") per il comportamento corretto dell'oggetto / metodo sotto test.

Per esempio:

class OrderInteractionTester...
  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send");
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
  }
}

Si noti che gli oggetti warehousee mailermock sono programmati con i risultati previsti.


2
La definizione che hai dato differisce non da ultimo da "oggetto stub" e come tale non spiega cosa sia un oggetto simulato.
Brent Arias,

Un'altra correzione "la parola 'Mock' è talvolta erroneamente usata in modo intercambiabile con 'stub'".
Brent Arias,

@Myst: l'uso delle due parole non è universale; varia tra gli autori. Fowler lo dice, e l'articolo di Wikipedia lo dice. Tuttavia, sentiti libero di modificare la modifica e rimuovere il tuo voto negativo. :)
Robert Harvey,

1
Sono d'accordo con Robert: l'uso della parola "mock" tende a variare in base al settore, ma non esiste una definizione definita in base alla mia esperienza, tranne che in genere NON è l'oggetto effettivamente testato, ma esiste per facilitare i test in cui si utilizza l'effettivo l'oggetto o tutte le sue parti sarebbero molto scomodi e di scarsa conseguenza.
mkelley33,

15

Gli oggetti simulati sono oggetti simulati che imitano il comportamento di quelli reali. In genere si scrive un oggetto finto se:

  • L'oggetto reale è troppo complesso per incorporarlo in un test unitario (ad esempio una comunicazione di rete, è possibile avere un oggetto simulato che simula l'altro peer)
  • Il risultato del tuo oggetto non è deterministico
  • L'oggetto reale non è ancora disponibile

12

Un oggetto Mock è un tipo di Test Double . Stai usando mockobjects per testare e verificare il protocollo / interazione della classe sotto test con altre classi.

Tipicamente, ti aspetterai di "programmare" o "registrare" le aspettative: chiamate di metodo che ti aspetti che la tua classe faccia a un oggetto sottostante.

Supponiamo ad esempio che stiamo testando un metodo di servizio per aggiornare un campo in un widget. E che nella tua architettura c'è un WidgetDAO che si occupa del database. Parlare con il database è lento e configurarlo e pulirlo in seguito è complicato, quindi derideremo WidgetDao.

pensiamo a cosa deve fare il servizio: dovrebbe ottenere un Widget dal database, fare qualcosa con esso e salvarlo di nuovo.

Quindi in pseudo-linguaggio con una libreria pseudo-finta avremmo qualcosa di simile:

Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);

// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);   
expect(mock.save(sampleWidget);

// turn the dao in replay mode
replay(mock);

svc.updateWidgetPrice(id,newPrice);

verify(mock);    // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());

In questo modo possiamo testare facilmente lo sviluppo di classi che dipendono da altre classi.



9

Quando si esegue il test unitario di una parte di un programma per computer, si desidera idealmente testare solo il comportamento di quella particolare parte.

Ad esempio, guarda lo pseudo-codice qui sotto da un pezzo immaginario di un programma che utilizza un altro programma per chiamare print qualcosa:

If theUserIsFred then
    Call Printer(HelloFred)
Else
   Call Printer(YouAreNotFred)
End

Se lo stavi testando, vorrai principalmente testare la parte che controlla se l'utente è Fred o no. Non vuoi davvero testare la Printerparte delle cose. Sarebbe un altro test.

È qui che entrano in gioco gli oggetti Mock. Fingono di essere altri tipi di cose. In questo caso useresti un Mock in Printermodo che si comporterebbe proprio come una vera stampante, ma non farebbe cose scomode come la stampa.


Ci sono molti altri tipi di oggetti finti che puoi usare che non sono Manichino. La cosa principale che rende Mocks Mock è che possono essere configurati con comportamenti e aspettative.

Le aspettative consentono a Mock di generare un errore se utilizzato in modo errato. Quindi nell'esempio sopra, potresti voler essere sicuro che la stampante venga chiamata con HelloFred nel caso di test "user is Fred". Se ciò non accade, il tuo Mock può avvisarti.

Behavior in Mocks significa che, ad esempio, del tuo codice ha fatto qualcosa del tipo:

If Call Printer(HelloFred) Returned SaidHello Then
    Do Something
End

Ora vuoi testare cosa fa il tuo codice quando viene chiamata la stampante e restituisce SaidHello, quindi puoi impostare il Mock per restituire SaidHello quando viene chiamato con HelloFred.

Una buona risorsa intorno a questo è Martin Fowlers post Mocks Aren't Stubs


7

Gli oggetti finti e stub sono una parte cruciale del test unitario. In effetti fanno molto per assicurarsi che tu stia testando le unità , piuttosto che i gruppi di unità.

In poche parole, si utilizzano gli stub per interrompere la dipendenza di SUT (System Under Test) da altri oggetti e simulazioni per farlo e verificare che SUT abbia chiamato determinati metodi / proprietà sulla dipendenza. Ciò risale ai principi fondamentali dei test unitari: i test dovrebbero essere facilmente leggibili, rapidi e non richiedono configurazione, cosa che potrebbe implicare l'uso di tutte le classi reali.

In genere, puoi avere più di un tronchetto nel test, ma dovresti avere solo un finto. Questo perché lo scopo di Mock è verificare il comportamento e il test dovrebbe testare solo una cosa.

Scenario semplice con C # e Moq:

public interface IInput {
  object Read();
}
public interface IOutput {
  void Write(object data);
}

class SUT {
  IInput input;
  IOutput output;

  public SUT (IInput input, IOutput output) {
    this.input = input;
    this.output = output;
  }

  void ReadAndWrite() { 
    var data = input.Read();
    output.Write(data);
  }
}

[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
  //we want to verify that SUT writes to the output interface
  //input is a stub, since we don't record any expectations
  Mock<IInput> input = new Mock<IInput>();
  //output is a mock, because we want to verify some behavior on it.
  Mock<IOutput> output = new Mock<IOutput>();

  var data = new object();
  input.Setup(i=>i.Read()).Returns(data);

  var sut = new SUT(input.Object, output.Object);
  //calling verify on a mock object makes the object a mock, with respect to method being verified.
  output.Verify(o=>o.Write(data));
}

Nell'esempio sopra ho usato Moq per dimostrare mozziconi e beffe. Moq usa la stessa classe per entrambi, il Mock<T>che la rende un po 'confusa. Indipendentemente da ciò, in fase di esecuzione, il test fallirà se output.Writenon viene chiamato con i dati come parameter, mentre la mancata chiamata input.Read()non lo fallirà.


4

Come un'altra risposta suggerita tramite un link a " MockS non sono tronconi ", i mock sono una forma di "test double" da usare al posto di un oggetto reale. Ciò che li rende diversi dalle altre forme di doppio test, come gli oggetti stub, è che altri doppi test offrono la verifica dello stato (e facoltativamente la simulazione) mentre i mock offrono la verifica del comportamento (e facoltativamente la simulazione).

Con uno stub, è possibile chiamare diversi metodi sullo stub in qualsiasi ordine (o anche in modo ripetitivo) e determinare il successo se lo stub ha acquisito un valore o lo stato desiderato. Al contrario, un oggetto simulato si aspetta che vengano chiamate funzioni molto specifiche, in un ordine specifico e persino un numero specifico di volte. Il test con un oggetto simulato verrà considerato "fallito" semplicemente perché i metodi sono stati invocati in una sequenza o conteggio diversi - anche se l'oggetto simulato aveva lo stato corretto al termine del test!

In questo modo, gli oggetti finti sono spesso considerati più strettamente accoppiati al codice SUT rispetto agli oggetti stub. Può essere una cosa positiva o negativa, a seconda di ciò che stai cercando di verificare.


3

Parte del punto di usare oggetti finti è che non devono essere realmente implementati secondo le specifiche. Possono solo dare risposte fittizie. Ad esempio, se devi implementare i componenti A e B, ed entrambi "chiamano" (interagiscono) tra loro, allora non puoi testare A finché non viene implementato B e viceversa. Nello sviluppo guidato dai test, questo è un problema. Quindi crei oggetti finti ("fittizi") per A e B, che sono molto semplici, ma danno una sorta di risposta quando interagiscono. In questo modo, puoi implementare e testare A usando un oggetto finto per B.


1

Per php e phpunit è ben spiegato nel documento phpunit. vedi qui la documentazione di phpunit

In parole semplici, l'oggetto di derisione è solo un oggetto fittizio del tuo originale e restituisce il suo valore di ritorno, questo valore di ritorno può essere usato nella classe di test


0

È una delle principali prospettive dei test unitari. Sì, stai provando a testare la tua singola unità di codice e i risultati del test non dovrebbero essere rilevanti per il comportamento di altri bean o oggetti. quindi dovresti deriderli usando oggetti simulati con una risposta corrispondente semplificata.

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.