A che punto del processo TDD dovrei prendere in giro?

1

Solo per ottenere confusione: sto iniziando dalla posizione in cui so già cosa deridere, dove e come farlo. Semplicemente non so come adattare il mocking al processo TDD.

Quindi diciamo che ho già fatto un my_function () e voglio scrivere MyClass () che usa my_function () da qualche parte. Diciamo che il mio obiettivo finale è qualcosa del genere:

class MyClass():
    (...)
    def some_method():
        var = my_function()
        self._do_something(var)

Scrivere questo in modo classico sarebbe facile:

  1. Scrivi il codice.
  2. Renditi conto che my_function () deve essere preso in giro perché contiene alcune connessioni e altro.
  3. Scrivi un my_function mock.
  4. Scrivi un test per MyClass (). some_method () usando la simulazione.
  5. Esegui il test.

Voglio farlo prima, però, ma finora mi è venuto in mente solo questo:

  1. Scrivi il test per MyClass (). some_method (), ignorando il mock.
  2. Esegui il test: fallisce.
  3. Scrivi il codice.
  4. Esegui il test - fallisce perché my_function () ha alcune dipendenze esterne e ha bisogno di un certo ambiente ecc.
  5. Scrivi la simulazione e modifica il test.
  6. Esegui il test - passa.

Questo approccio sembra però imperfetto. Prima scrivo il test in modo da poterlo eseguire ancora e ancora mentre scrivo il codice, aspettando che diventi verde. Con il mio approccio il mio test non diventerà mai verde prima di decidere che sono "fatto" con il codice e solo dopo inizierò a deridere (perché non saprò cosa deridere prima di scrivere il codice). Inoltre sto effettivamente cambiando il test in modo che usi la simulazione, mentre il codice è già "finito", quindi sto facendo il test per verificare effettivamente se ho finito di cambiare il test, e non cambiare il codice (spero di averlo ho un senso qui).

Che ne pensi? Il mio processo TDD è difettoso o va bene e il mio ragionamento su di esso è difettoso? Ho provato a cercare in rete ma non ho trovato nessuno che discutesse o spiegasse questo particolare aspetto. O è solo beffardo, supponendo che il codice sia già stato scritto, o sia TDD e l'ipotesi è che non si hanno dipendenze da prendere in giro.

    
posta iknownothing 22.05.2017 - 14:35
fonte

4 risposte

1

Naturalmente non puoi prendere in giro la dipendenza prima di rendersi conto che c'è una dipendenza, quindi a questo proposito quello che stai facendo va bene. Ma potresti chiederti perché ci sia voluto un test fallito dopo aver scritto il codice per notare la dipendenza e cosa potresti fare al riguardo.

Non hai notato la dipendenza perché non era esplicita, l'hai scoperta solo quando hai eseguito il test. La saggezza del TDD convenzionale dice che a questo punto dovresti riconsiderare il tuo progetto per rendere esplicita la dipendenza, la chiamano iniezione di dipendenza. I modi più comuni per farlo sono passare my_function come argomento a MyClass.some_method o MyClass.__init__ .

Utilizzando l'iniezione di dipendenza, i test diventano più facili da scrivere e leggere:

  1. Ti sei perso la dipendenza perché non era ovvio. Rendendolo esplicito è più facile prendere in considerazione tutte le dipendenze necessarie durante la scrittura del test.

  2. Stavi deridendo un dettaglio di implementazione di MyClass.some_method . Ciò potrebbe confondere le altre persone che leggono il test, dovrebbero guardare il codice di implementazione per capire perché stai creando un oggetto che il test non usa. L'iniezione delle dipendenze rende più facile la comprensione perché l'oggetto viene passato a una chiamata di metodo nel test.

risposta data 22.05.2017 - 17:36
fonte
0

Una caratteristica chiave di "UnitTest" è che verifica il comportamento dell'unità testata in isolamento .

Quindi non decidi se il mocking è necessario per la complessità di my_function() ma se appartiene alla stessa unità .

Secondo Roy Osherove (The Art Of UnitTesting) appartiene alla stessa unità se ha lo stesso motivo per cambiare .

Poiché my_function() esiste già (con il proprio set di unittests), la maggior parte ha diverse ragioni per cambiare e (di conseguenza) è o appartiene a una diversa unità.

Poiché è / appartiene a un'unità diversa, deve essere deriso per il test corrente.

I'm asking "at which point in the TDD process should I actually write the mock and why". – iknownothing

Scrivi la simulazione quando crei il test non appena ti accorgi che comunicare con una dipendenza ( my_function() ) fa parte del comportamento desiderato dell'unità corrente. E il motivo è di mantenere il comportamento delle unità correnti isolato per il test.

    
risposta data 22.05.2017 - 14:52
fonte
0

Scrivo prima la parte Assert dei miei test, e non ho mai avuto problemi a capire quando introdurre mock (o stub), perché è un seguito logico a ciò su cui si sta facendo valere. L'introduzione di oggetti finti di solito arriva nelle fasi iniziali della fase rossa.

  • Nei test basati su mock , sai che devi verificare le cose registrate dal mock perché ciò che importa è che la dipendenza esterna che rappresenta è stata chiamata correttamente. Quindi scrivi la prima cosa Assert e vai subito alla parte Arrange immediatamente dopo, per dichiarare i mock che hai appena verificato nella parte Assert . Quindi istanziati la tua classe sotto test, in qualche modo iniettandola con i mock e completa la sezione Act .

    Da lì, tutto si compila ma il test è ancora rosso. Poi vai a scrivere il codice di produzione che lo farà passare.

  • Stub , al contrario, sono qui per fornire input predefiniti. Nei test che li usano, qualcosa negli Assert è solitamente collegato a un valore dato dallo stub. Ad esempio, puoi eliminare un repository per restituire un oggetto arbitrario A. Con ogni probabilità, qualcosa nelle tue asserzioni sarà correlato all'oggetto A, altrimenti non ti preoccuperai di impostare uno stub restituendolo. La scrittura delle asserzioni ti porterà ad usare l'oggetto A e quindi ti costringerà a scrivere lo stub che lo restituisce se vuoi che il test compili.

  • Se appare una nuova dipendenza a seguito del refactoring di una classe, il test originale per la classe dovrebbe comunque funzionare e coprire tutto ciò di cui hai bisogno. Puoi rifattorizzare il tuo test per eliminare la dipendenza e introdurre un altro test dedicato alla nuova classe, se vuoi, anche se non è un obbligo - quindi sarebbe un altro momento legittimo per introdurre una simulazione, suppongo.

risposta data 22.05.2017 - 17:49
fonte
0

Dato che my_function "fa connessioni e cose", immagino lo faccia per un motivo, che verifichi nel test. Quindi, nel momento in cui scrivi una dichiarazione di asserzione per verificare l'esito, probabilmente già ti accorgi che il codice dovrà effettuare delle connessioni per far passare il test. Se lo fai, è OK introdurre la simulazione in quel punto.

Se non lo realizzi in quel momento, lo farai al punto 3 quando scrivi il codice. A quel punto dovresti solo cambiare il codice setup del tuo test (per iniettare il mock e fargli restituire una risposta appropriata per la chiamata effettuata durante il test). Non dovresti dover cambiare il test stesso.

In alcuni casi, restituire una risposta appropriata (o fare tutto il necessario per produrre un risultato che può essere verificato) risulta troppo difficile, e preferisci semplicemente verificare che la funzione sia stata chiamata con argomenti corretti. In tal caso puoi scegliere di deviare dalla tecnica normale. Si tratta di trade-off.

    
risposta data 23.05.2017 - 12:07
fonte

Leggi altre domande sui tag