2013-01-22 5 views
5

Przetwarzam sekwencję obiektów zdefiniowanych przez użytkownika. To wygląda podobnie do następującego:Jak mogę wywoływać połączenia akceptujące argumenty sekwencji za pomocą Python Mock?

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

Metoda Ja obecnie testuje posiada funkcje podobne do następującego:

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 

Problem jestem stoi testuje połączenia do calculate_something. Chcę stwierdzić, że te rozmowy się stało, coś tak:

calculateSomethingMock.assert_any_call(the_sequence) 

Nie dbam o kolejności sekwencji przeszedł do calculate_something, ale nie obchodzi, że wszystkie elementy są obecne. Mógłbym zawinąć funkcję generatora w wywołaniu set, ale nie mam ochoty, aby mój test dyktował, jaki rodzaj sekwencji został przekazany do calculate_something. Powinienem móc przekazać mu jakąś sekwencję. Mogłem alternatywnie stworzyć metodę, która generuje sekwencję zamiast używać składni generatora i kpić z tej metody, ale to wydaje się przesadą.

Jak najlepiej sformułować to stwierdzenie, czy też moje problemy z testowaniem tutaj wskazują na słabo skonstruowany kod?

Używam Python 2.7.3 z Mock 1.0.1.

(dla każdego, kto czuje się zmuszony do wypowiedzieć się na to, że jestem świadomy robię próbę ostatni i że to nie jest uważany za największego praktyka).

Edit:

Po zegarka this marvelous talk entitled "Why You Don't Get Mock Objects by Gregory Moeck" Zastanowiłem się, czy powinienem kpić z metody calculate_something.

Odpowiedz

2

Nie tknąłem kodu, nad którym początkowo pracowałem przez jakiś czas, ale wracam do mojego podejścia do testów w ogóle.Staram się być bardziej ostrożny, co robię i nie kpię. Niedawno zdałem sobie sprawę, że podświadomie podążam za tą regułą: drwić z czegoś, co sprawia, że ​​mój test jest krótszy i prostszy, i zostaw go w spokoju, jeśli sprawi, że test będzie bardziej skomplikowany. Proste testowanie wejścia/wyjścia wystarcza w przypadku tej metody. Nie ma zewnętrznych zależności, takich jak baza danych lub pliki. Krótko mówiąc, myślę, że odpowiedź na moje pytanie brzmi: "Nie powinienem kpić z calculate_something." Dzięki temu mój test będzie trudniejszy do odczytania i utrzymania.

7

Patrząc na dokumentację próbną, jest tam call_args_list, który zrobi to, co chcesz.

Wykopujesz calculate_something podczas testu.

calculate_something = Mock(return_value=None) 

Po my_function zakończeniu można sprawdzić argumenty przekazywane przez robi:

calculate_something.call_args_list 

który zwróci listę wszystkich połączeń wykonywanych do niego (wraz z odnośnymi elementami minęła).

Edit:

(Niestety zajęło mi tak długo, musiałem zainstalować Python3.3 na moim komputerze)

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 

plik_testowy. 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

Próbuję uniknąć zagłębiania się w tę listę, ale przypuszczam, że to może być jedyny sposób. Czy możesz podać przykład? – jpmc26

+0

Dzięki. Ten test opiera się na metodzie zachowywania porządku, a także opiera się na metodzie przechodzenia na listę. Nie bardzo zależy mi na teście polegającym na zamówieniu. Jeśli zdecyduję się zmienić na inną strukturę danych dla argumentu przekazanego do 'calcul_something', czy chcę, żeby test się nie powiódł? Lub odwrotnie, jeśli zmienię test, aby sprawdzić wywołanie innego typu danych, czy chcę zmodyfikować metodę, aby ponownie go przekazać? – jpmc26

+0

Tak, ale pamiętaj, że to jest przykład. Można przekazać 'calcul_something' dowolną inną strukturę danych, wystarczy zaktualizować test, aby to odzwierciedlić (Zmieniając oczekiwane wartości). Chcesz, żeby test przeszedł mimo wszystko. Już widziałeś, jak uzyskać argumenty przekazane do 'calcul_something' używając' call_args_list'. Później chodzi tylko o porównanie danych uporządkowanych według tego, czego oczekujesz. –

Powiązane problemy