2013-04-04 17 views
38

W mojej próbie nauczenia się TDD, próbując nauczyć się testowania jednostek i używania makiety z pytonem. Powoli się do tego przyzwyczaja, ale nie wiem, czy robię to poprawnie. Ostrzeżony: Utknąłem przy użyciu Pythona 2.4, ponieważ API dostawcy przychodzą jako wstępnie skompilowane pliki 2.4 Pyc, więc używam makiety 0.8.0 i unittest (nie unittest2)Jak poprawnie używać makiety w python z unittest setUp

Podany tutaj przykładowy kod w 'mymodule.py "

import ldap 

class MyCustomException(Exception): 
    pass 

class MyClass: 
    def __init__(self, server, user, passwd): 
     self.ldap = ldap.initialize(server) 
     self.user = user 
     self.passwd = passwd 

    def connect(self): 
     try: 
      self.ldap.simple_bind_s(self.user, self.passwd) 
     except ldap.INVALID_CREDENTIALS: 
      # do some stuff 
      raise MyCustomException 

teraz w moim przypadku testowego pliku«test_myclass.py», chcę drwić LDAP obiektu na zewnątrz. ldap.initialize zwraca ldap.ldapobject.SimpleLDAPObject, więc pomyślałem, że to będzie metoda, którą będę musiał wyszydzić.

import unittest 
from ldap import INVALID_CREDENTIALS 
from mock import patch, MagicMock 
from mymodule import MyClass 

class LDAPConnTests(unittest.TestCase): 
    @patch('ldap.initialize') 
    def setUp(self, mock_obj): 
     self.ldapserver = MyClass('myserver','myuser','mypass') 
     self.mocked_inst = mock_obj.return_value 

    def testRaisesMyCustomException(self): 
     self.mocked_inst.simple_bind_s = MagicMock() 
     # set our side effect to the ldap exception to raise 
     self.mocked_inst.simple_bind_s.side_effect = INVALID_CREDENTIALS 
     self.assertRaises(mymodule.MyCustomException, self.ldapserver.connect) 

    def testMyNextTestCase(self): 
     # blah blah 

prowadzi mnie do kilku pytań:

  1. to wygląda prawda? :)
  2. Czy to właściwy sposób na próbowanie i wyśmiewanie obiektu, który zostanie zainicjowany w klasie, którą testuję?
  3. Czy można wywoływać dekorator @patch na setUp, czy może spowoduje to dziwne efekty uboczne?
  4. Czy mimo to można próbować podnieść wyjątek ldap.INVALID_CREDENTIALS bez konieczności importowania wyjątku do mojego pliku testowego?
  5. Czy zamiast tego powinienem używać patch.object(), a jeśli tak, to w jaki sposób?

Dzięki.

+1

1-3) wydaje się dobrze do mnie ... 4) 'import ldap' zamiast i ustawić' side_effect = ldap.INVALID_CREDENTIALS'? – Chris

+0

Zawsze można wykonać ten sam test, ale z prostymi obiektami wykonanymi przez siebie ... – shackra

Odpowiedz

36

Patrz: 26.5.3.4. Applying the same patch to every test method

To sprawia, że ​​bardziej sensowne, aby skonfigurować Łatacz ten sposób na temat konfiguracji, jeśli chcesz, łatanie do zrobienia dla wszystkich metod badawczych.

+2

To samo dotyczy pre-Python3 Mock (hostowane na http://www.voidspace.org.uk/python/mock/), zobacz [Stosowanie tej samej poprawki do każdej metody testowej] (http://www.voidspace.org.uk/python/mock/examples.html#applying-the-same-patch-to-every-test-metod). – musiphil

+2

Po prostu napotkałem problem, w którym miałem symulację na poziomie klasy w klasie TestCase i założyłem, że będzie ona już na miejscu podczas wykonywania połączenia w metodzie "setUp()". NIE O TO CHODZI; mocks klasy nie są stosowane w czasie do użycia w 'setUp()'. Rozwiązałem problem, tworząc zamiast tego metodę pomocniczą, której używam w ramach wszystkich moich testów. Nie jestem pewien, czy to jest najlepsze podejście, ale działa. – berto

+0

@berto Jeśli rozwiniesz swój komentarz w odpowiedzi, myślę, że będzie to pomocne. To inne i prawdopodobnie łatwiejsze rozwiązanie niż inne tutaj. – KobeJohn

4

Jeśli masz wiele poprawek do zastosowania i chcesz je zastosować do rzeczy zainicjowany metod Setup zbyt spróbuj tego:

def setUp(self): 
    self.patches = { 
     "sut.BaseTestRunner._acquire_slot": mock.Mock(), 
     "sut.GetResource": mock.Mock(spec=GetResource), 
     "sut.models": mock.Mock(spec=models), 
     "sut.DbApi": make_db_api_mock() 
    } 

    self.applied_patches = [mock.patch(patch, data) for patch, data in self.patches.items()] 
    [patch.apply for patch in self.applied_patches] 
    . 
    . rest of setup 
    . 


def tearDown(self): 
    patch.stop_all() 
+3

rozważ użycie 'łatki.stop_all()' w 'tearDown()'. –

+0

Próbowałem tego - wydaje się, że apply_patches również trzeba uruchomić. Rozważmy linię typu: 'dla poprawki w self.applied_patches: patch.start()' – F1Rumors

3

Zacznę od odpowiedzi na swoje pytania, a potem dam szczegółowy przykład interakcji między patch() i setUp().

  1. Nie sądzę, że wygląda prawidłowo, patrz odpowiedź nr 3, aby uzyskać szczegółowe informacje.
  2. Tak, faktyczne wywołanie poprawki wygląda tak, jak powinno się kpić z wybranego obiektu.
  3. Nie, prawie nigdy nie chcesz używać dekoratora @patch() na setUp(). Masz szczęście, ponieważ obiekt jest tworzony w setUp() i nigdy nie zostanie utworzony podczas testu.
  4. Nie wiem, w jaki sposób można sfałszować obiekt wywołać wyjątek bez importowania tego wyjątku do pliku testowego.
  5. Nie widzę potrzeby tutaj patch.object(). Pozwala tylko łatać atrybuty obiektu, zamiast określać cel jako łańcuch.

Aby rozwinąć w odpowiedzi nr 3, problem polega na tym, że dekorator patch() ma zastosowanie tylko wtedy, gdy działa dekorowana funkcja. Po powrocie setUp() łatka jest usuwana.W twoim przypadku to działa, ale założę się, że to zmyliłoby kogoś patrzącego na ten test. Jeśli naprawdę chcesz, aby łatka się wydarzyła podczas setUp(), sugerowałbym użycie oświadczenia with, aby było oczywiste, że łatka zostanie usunięta.

Poniższy przykład ma dwa przypadki testowe. TestPatchAsDecorator pokazuje, że dekorowanie klasy będzie stosować łatkę podczas metody testowania, ale nie podczas setUp(). TestPatchInSetUp pokazuje, w jaki sposób można zastosować poprawkę tak, aby była zainstalowana zarówno podczas setUp(), jak i metody testowania. Wywołanie self.addCleanUp() powoduje, że łatka zostanie usunięta podczas tearDown().

import unittest 
from mock import patch 


@patch('__builtin__.sum', return_value=99) 
class TestPatchAsDecorator(unittest.TestCase): 
    def setUp(self): 
     s = sum([1, 2, 3]) 

     self.assertEqual(6, s) 

    def test_sum(self, mock_sum): 
     s1 = sum([1, 2, 3]) 
     mock_sum.return_value = 42 
     s2 = sum([1, 2, 3]) 

     self.assertEqual(99, s1) 
     self.assertEqual(42, s2) 


class TestPatchInSetUp(unittest.TestCase): 
    def setUp(self): 
     patcher = patch('__builtin__.sum', return_value=99) 
     self.mock_sum = patcher.start() 
     self.addCleanup(patcher.stop) 

     s = sum([1, 2, 3]) 

     self.assertEqual(99, s) 

    def test_sum(self): 
     s1 = sum([1, 2, 3]) 
     self.mock_sum.return_value = 42 
     s2 = sum([1, 2, 3]) 

     self.assertEqual(99, s1) 
     self.assertEqual(42, s2)