2015-03-28 12 views
7

devo patchare tre metodi (_send_reply, _reset_watchdog e _handle_set_watchdog) con metodi finte prima di testare una chiamata a un quarto metodo (_handle_command) in un test di unità di mine.modo preferito di patch diversi metodi in Python unit test

Guardando la documentazione per il pacchetto mock, ci sono alcuni modi che potrei andare a questo proposito:

Con patch.multiple come decoratrice

@patch.multiple(MBG120Simulator, 
       _send_reply=DEFAULT, 
       _reset_watchdog=DEFAULT, 
       _handle_set_watchdog=DEFAULT, 
       autospec=True) 
def test_handle_command_too_short_v1(self, 
            _send_reply, 
            _reset_watchdog, 
            _handle_set_watchdog): 
    simulator = MBG120Simulator() 
    simulator._handle_command('XA99') 
    _send_reply.assert_called_once_with(simulator, 'X?') 
    self.assertFalse(_reset_watchdog.called) 
    self.assertFalse(_handle_set_watchdog.called) 
    simulator.stop() 

Con patch.multiple come contesto direttore

def test_handle_command_too_short_v2(self): 
    simulator = MBG120Simulator() 

    with patch.multiple(simulator, 
         _send_reply=DEFAULT, 
         _reset_watchdog=DEFAULT, 
         _handle_set_watchdog=DEFAULT, 
         autospec=True) as mocks: 
     simulator._handle_command('XA99') 
     mocks['_send_reply'].assert_called_once_with('X?') 
     self.assertFalse(mocks['_reset_watchdog'].called) 
     self.assertFalse(mocks['_handle_set_watchdog'].called) 
     simulator.stop() 

Con multipla patch.object decoratorations

@patch.object(MBG120Simulator, '_send_reply', autospec=True) 
@patch.object(MBG120Simulator, '_reset_watchdog', autospec=True) 
@patch.object(MBG120Simulator, '_handle_set_watchdog', autospec=True) 
def test_handle_command_too_short_v3(self, 
            _handle_set_watchdog_mock, 
            _reset_watchdog_mock, 
            _send_reply_mock): 
    simulator = MBG120Simulator() 
    simulator._handle_command('XA99') 
    _send_reply_mock.assert_called_once_with(simulator, 'X?') 
    self.assertFalse(_reset_watchdog_mock.called) 
    self.assertFalse(_handle_set_watchdog_mock.called) 
    simulator.stop() 

metodi che sostituiscono manualmente utilizzando create_autospec

def test_handle_command_too_short_v4(self): 
    simulator = MBG120Simulator() 

    # Mock some methods. 
    simulator._send_reply = create_autospec(simulator._send_reply) 
    simulator._reset_watchdog = create_autospec(simulator._reset_watchdog) 
    simulator._handle_set_watchdog = create_autospec(simulator._handle_set_watchdog) 

    # Exercise. 
    simulator._handle_command('XA99') 

    # Check. 
    simulator._send_reply.assert_called_once_with('X?') 
    self.assertFalse(simulator._reset_watchdog.called) 
    self.assertFalse(simulator._handle_set_watchdog.called) 

Personalmente penso che l'ultimo è più chiaro da leggere, e non si tradurrà in orribilmente lunghe file se il numero dei metodi derisi crescono. Evita anche di passare in simulator come il primo argomento (self) a assert_called_once_with.

Ma non li trovo particolarmente belli. Soprattutto l'approccio multiplo patch.object, che richiede un'accurata corrispondenza dell'ordine dei parametri con le decorazioni nidificate.

C'è qualche approccio che ho perso, o un modo per renderlo più leggibile? Che cosa fai quando hai bisogno di applicare più metodi all'istanza/classe sotto test?

risposta

4

No non ti sei perso nulla di veramente diverso da quello che hai proposto.

A proposito di leggibilità, il mio gusto è per il decoratore, perché rimuove il materiale derisorio dal corpo del test ... ma è solo gusto.

Avete ragione: se si patch l'istanza statica del metodo di autospec=True è necessario utilizzare autonomamente nei metodi di controllo famiglia assert_called_*. Ma il tuo caso è solo una piccola classe perché sai esattamente quale oggetto devi applicare e non hai davvero bisogno di altro contesto per la tua patch rispetto al metodo di prova.

È sufficiente applicare una patch all'oggetto per utilizzarlo per tutti i test: spesso nei test non è possibile applicare l'istanza alla patch prima di effettuare la chiamata e in questi casi non è possibile utilizzare create_autospec: è possibile applicare la patch all'istanza statica dei metodi anziché.

Se si è infastiditi passando l'istanza ai metodi assert_called_*, prendere in considerazione l'uso di ANY per interrompere la dipendenza. Alla fine ho scritto centinaia di test del genere e non ho mai avuto problemi con l'ordine degli argomenti.

Il mio approccio standard al vostro test è

@patch('mbgmodule.MBG120Simulator._send_reply', autospec=True) 
@patch('mbgmodule.MBG120Simulator._reset_watchdog', autospec=True) 
@patch('mbgmodule.MBG120Simulator._handle_set_watchdog', autospec=True) 
def test_handle_command_too_short(self,mock_handle_set_watchdog, 
              mock_reset_watchdog, 
              mock_send_reply): 
    simulator = MBG120Simulator() 
    simulator._handle_command('XA99') 
    # You can use ANY instead simulator if you don't know it 
    mock_send_reply.assert_called_once_with(simulator, 'X?') 
    self.assertFalse(mock_reset_watchdog.called) 
    self.assertFalse(mock_handle_set_watchdog_mock.called) 
    simulator.stop() 
  • Patching è fuori dal codice del metodo di prova
  • Ogni inizia finte da mock_ prefisso
  • Io preferisco usare semplice patch chiamata e percorso assoluto : è chiaro e preciso quello che stai facendo

Infine: maggio essere creato simulator e fermarsi sono setUp() e tearDown() responsabilità e test dovrebbero prendere in considerazione solo per correggere alcuni metodi e fare i controlli.

Spero che la risposta sia utile ma la domanda non ha una risposta valida univoca in quanto la leggibilità non è un concetto assoluto e dipende dal lettore. Inoltre, anche il titolo che parla del caso generale, gli esempi di domande riguardano la classe specifica di problemi in cui è necessario ricorrere ai metodi dell'oggetto da testare.

[EDIT]

ho pensato un po 'su questa questione e ho trovato quello che mi disturba: si sta cercando di testare e rilevare sui metodi privati. Quando ciò accade, la prima cosa che dovresti chiedere è perché? Ci sono molte possibilità che la risposta sia perché questi metodi dovrebbero essere metodi pubblici di collaboratori privati ​​(that not my words).

In questo nuovo scenario si dovrebbe percepire su collaboratori privati ​​e non è possibile modificare solo il proprio oggetto. Quello che devi fare è applicare una patch all'istanza statica di alcune altre classi.

+0

Grazie mille per la risposta completa. In ordine: 1) Buon punto sul fatto che il mio caso è speciale (che ho accesso all'istanza et.c.). 2) Grazie per il consiglio su QUALSIASI. 3) Sì, forse sto esagerando con la fragilità dell'ordine delle argomentazioni quando uso i decoratori. 4) Domanda: Quindi '@patch ('package.module.Class.some_method', autospec = True)' è equivalente a '@ patch.object (package.module.Class, 'some_method', autospec = True)'? È carino, e se è così preferisco anche il tuo modo di farlo. ... – estan

+0

... 5) sono un po 'titubante per usare '' setUp' e tearDown' come mi piace il mio test di essere completamente autonomo, nonostante sapessero che vorrà dire un po' di battitura. Ma questa è solo una preferenza personale. 6) .. e su quella nota, sì mi dispiace per aver fatto una domanda che ha solo risposte prevalentemente soggettive! 7) Prenderò in considerazione ciò che hai detto sul test dei metodi privati. Mi piace essere in grado di farlo facilmente con Python ed è importante che questo codice abbia una copertura della linea del 100%. Ma prenderò in considerazione la possibilità di suddividerli in collaboratori. La classe sotto test è abbastanza piccola però. Ancora una volta, grazie per l'ottima risposta. – estan

+0

Gah. Scusa per la formattazione orribile. Non sono davvero amico della funzionalità dei commenti SO. Ad ogni modo, accettando la tua risposta in quanto è molto buona. – estan