Hai ragione, i tuoi test non dovrebbero verificare che il modulo random
stia facendo il suo lavoro; un unittest dovrebbe testare solo la classe stessa, non come interagisce con altri codici (che dovrebbero essere testati separatamente).
Ovviamente è del tutto possibile che il tuo codice utilizzi random.randint()
errato; o invece stai chiamando random.randrange(1, self._sides)
e il tuo dado non getta mai il valore più alto, ma sarebbe un tipo diverso di bug, non uno che potresti catturare con un unittest. In questo caso, il tuo die
unità funziona come progettato, ma il design stesso era difettoso.
In questo caso, userei il mocking per sostituire la funzione randint()
, e verificare solo che sia stato chiamato correttamente. Python 3.3 e versioni successive sono dotati del modulo unittest.mock
per gestire questo tipo di test, ma è possibile installare il pacchetto mock
esterno su versioni precedenti per ottenere la stessa identica funzionalità
import unittest
try:
from unittest.mock import patch
except ImportError:
# < python 3.3
from mock import patch
@patch('random.randint', return_value=3)
class TestDice(unittest.TestCase):
def _make_one(self, *args, **kw):
from die import Die
return Die(*args, **kw)
def test_standard_size(self, mocked_randint):
die = self._make_one()
result = die.roll()
mocked_randint.assert_called_with(1, 6)
self.assertEqual(result, 3)
def test_custom_size(self, mocked_randint):
die = self._make_one(sides=42)
result = die.roll()
mocked_randint.assert_called_with(1, 42)
self.assertEqual(result, 3)
if __name__ == '__main__':
unittest.main()
Con il mocking, il tuo test è ora molto semplice; ci sono solo 2 casi, davvero. Il caso predefinito per un dado a 6 facce e il caso per i lati personalizzati.
Esistono altri modi per sostituire temporaneamente la funzione randint()
nello spazio dei nomi globale di Die
, ma il modulo mock
lo rende più semplice. Il @mock.patch
decoratore qui si applica a tutti metodi di prova nel caso di test; ogni metodo di test viene passato un argomento extra, la funzione random.randint()
derisa, quindi possiamo testare la simulazione per vedere se è stata effettivamente chiamata correttamente. L'argomento return_value
specifica cosa viene restituito dal mock quando viene chiamato, quindi possiamo verificare che il metodo die.roll()
abbia effettivamente restituito il risultato 'casuale' a noi.
Ho usato un'altra best practice inoppugnante di Python qui: importa la classe sotto test come parte del test. Il metodo _make_one
esegue l'importazione e l'istanza di lavoro all'interno di un test , in modo che il modulo di test continui a essere caricato anche se hai fatto un errore di sintassi o un altro errore che impedire l'importazione del modulo originale.
In questo modo, se hai commesso un errore nel codice del modulo stesso, i test verranno comunque eseguiti; falliranno, ti spiegheranno l'errore nel tuo codice.
Per essere chiari, i test di cui sopra sono semplicistici all'estremo. L'obiettivo qui non è di testare che random.randint()
sia stato chiamato con gli argomenti giusti, per esempio. Invece, l'obiettivo è quello di verificare che l'unità produca i risultati corretti in base a determinati input, in cui tali input includono i risultati delle altre unità non sotto test. Sfruttando il metodo random.randint()
puoi prendere il controllo su un altro input del tuo codice.
Nei test nel mondo reale , il codice effettivo nella tua unità sotto test sarà più complesso; la relazione con gli input passati all'API e il modo in cui le altre unità vengono invocate possono essere comunque interessanti, e il mocking ti darà accesso ai risultati intermedi e ti consentirà di impostare i valori di ritorno per tali chiamate.
Ad esempio, nel codice che autentica gli utenti rispetto a un servizio OAuth2 di terzi (un'interazione a più stadi), vuoi verificare che il tuo codice stia trasmettendo i dati giusti a quel servizio di terze parti e ti consente di simulare diversi errori risposte che il servizio di terze parti restituirebbe, consentendo di simulare diversi scenari senza dover creare autonomamente un server OAuth2 completo. Qui è importante verificare che le informazioni provenienti da una prima risposta siano state gestite correttamente e siano state inoltrate a una seconda fase di chiamata, quindi vuoi vedere che il servizio deriso viene chiamato correttamente.