2013-01-22 13 views
5

Sto elaborando una sequenza di oggetti definiti dall'utente. Sembra simile al seguente:Come posso affermare le chiamate che accettano gli argomenti di sequenza con Python Mock?

class Thing(object): 
    def __init__(self, x, y): 
     self.x = x 
     self.y = y 

Il metodo attualmente sto test hanno una funzionalità simile al seguente:

def my_function(things): 
    x_calc = calculate_something(t.x for t in things) 
    y_calc = calculate_something(t.y for t in things) 
    return x_calc/y_calc 

Il problema che sto affrontando sta testando le chiamate a calculate_something. Voglio affermare che queste chiamate è successo, qualcosa in questo modo:

calculateSomethingMock.assert_any_call(the_sequence) 

Non mi interessa circa l'ordine della sequenza passato in calculate_something, ma io mi interessa che gli elementi sono tutti presenti. Potrei avvolgere la funzione del generatore in una chiamata a set, ma non mi sento come se il mio test dovesse dettare quale tipo di sequenza è passato a calculate_something. Dovrei essere in grado di passargli qualsiasi tipo di sequenza. In alternativa, potrei creare un metodo che generi la sequenza invece di usare la sintassi del generatore e simulare quel metodo, ma sembra eccessivo.

Come posso strutturare meglio questa asserzione, o il mio problema è verificare qui un'indicazione di codice mal strutturato?

Sto usando Python 2.7.3 con Mock 1.0.1.

(Per chi si sente in dovere di commentare su di esso, mi rendo conto che sto facendo ultimo test e che questo non è considerato il più grande pratica.)

Edit:

Dopo orologio this marvelous talk entitled "Why You Don't Get Mock Objects by Gregory Moeck" , Ho riconsiderato se avrei dovuto persino prendere in giro il metodo calculate_something.

risposta

2

Non ho ancora toccato il codice con cui inizialmente lavoravo da un po ', ma ho riconsiderato il mio approccio ai test in generale.Ho cercato di essere più attento a quello che faccio e non sto scherzando. Recentemente mi sono reso conto che inconsciamente stavo iniziando a seguire questa regola: prendermi in giro qualcosa se rendesse il mio test più breve e più semplice e lasciarlo da solo se rende il test più complicato. Il semplice test di input/output è sufficiente nel caso di questo metodo. Non ci sono dipendenze esterne come un database o file. Quindi, in breve, penso che la risposta alla mia domanda sia: "Non dovrei prendere in giro calculate_something". Farlo rende più difficile la lettura e la manutenzione del mio test.

7

Guardando la documentazione di Mock, c'è un call_args_list che farà quello che vuoi.

Quindi prendi in giro calculate_something durante il test.

calculate_something = Mock(return_value=None) 

Dopo aver my_function ha terminato è possibile controllare gli argomenti passati facendo:

calculate_something.call_args_list 

che restituirà un elenco di tutte le chiamate effettuate ad esso (con i corrispondenti elementi passati).

Edit:

(Ci dispiace che mi ha portato così a lungo, ho dovuto installare Python3.3 sulla mia macchina)

mymodule.py

class Thing: 
    ... 
def calculate_something: 
    ... 

def my_function(things): 
    # Create the list outside in order to avoid a generator object 
    # from being passed to the Mock object. 

    xs = [t.x for t in things] 
    x_calc = calculate_something(xs) 

    ys = [t.y for t in things] 
    y_calc = calculate_something(ys) 
    return True 

file_prova. py

import unittest 
from unittest.mock import patch, call 
import mymodule 



class TestFoo(unittest.TestCase): 

    # You can patch calculate_something here or 
    # do so inside the test body with 
    # mymodule.calcualte_something = Mock() 
    @patch('mymodule.calculate_something') 
    def test_mock(self, mock_calculate): 

     things = [mymodule.Thing(3, 4), mymodule.Thing(7, 8)] 

     mymodule.my_function(things) 

     # call_args_list returns [call([3, 7]), call([4, 8])] 
     callresult = mock_calculate.call_args_list 


     # Create our own call() objects to compare against 
     xargs = call([3, 7]) 
     yargs = call([4, 8]) 

     self.assertEqual(callresult, [xargs, yargs]) 

     # or 
     # we can extract the call() info 
     # http://www.voidspace.org.uk/python/mock/helpers.html#mock.call.call_list 
     xargs, _ = callresult[0] 
     yargs, _ = callresult[1] 

     xexpected = [3, 7] 
     yexpected = [4, 8] 

     self.assertEqual(xargs[0], xexpected) 
     self.assertEqual(yargs[0], yexpected) 

if __name__ == '__main__': 
    unittest.main() 
+0

Cerco di evitare di scavare in quella lista, ma suppongo che potrebbe essere l'unico modo. Potresti fornire un esempio? – jpmc26

+0

Grazie. Questo test si basa sul metodo che preserva l'ordine e si basa anche sul metodo che passa in una lista. Non sono molto entusiasta del test basandosi sull'ordine. Se decido di passare ad un'altra struttura dati per l'argomento passato in 'calculate_something', voglio che il test fallisca? Oppure, viceversa, se cambio il test per verificare la chiamata per un tipo di dati diverso, devo modificare il metodo per farlo passare di nuovo? – jpmc26

+0

Sì, ma ricorda che questo è un esempio. È possibile passare a 'calculate_something' qualsiasi altra struttura di dati, basta aggiornare il test per riflettere quello (modificando i valori previsti). Vuoi che il test passi dopo tutto. Hai già visto come ottenere gli argomenti passati a 'calculate_something' usando' call_args_list'. Dopodiché, si tratta solo di confrontare i dati strutturati passati con quello che ti aspetti. –

Problemi correlati