2013-04-04 2 views
38

В моей попытке узнать TDD, пытаясь изучить модульное тестирование и использовать mock с python. Постепенно выискивайте его, но не уверены, правильно ли я это делаю. Предупреждаем: я использую python 2.4, потому что API-интерфейс поставщика поставляется в виде предварительно скомпилированных файлов 2.4 pyc, поэтому я использую mock 0.8.0 и unittest (не unittest2)Как правильно использовать mock в python с unittest setUp

С учетом этого примера кода в '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 

Теперь в моем тестовом дела „test_myclass.py“, я хочу, чтобы дразнить объект LDAP вне. ldap.initialize возвращает ldap.ldapobject.SimpleLDAPObject, поэтому я решил, что это будет метод, который мне придется издеваться.

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 

ведет меня пару вопросов:

  1. это выглядит не так ли? :)
  2. Это правильный способ попробовать и издеваться над объектом, который создается в классе, который я тестирую?
  3. Можно ли назвать декоратор @patch на setUp или это вызовет странные побочные эффекты?
  4. Есть ли все-таки, чтобы получить mock, чтобы поднять исключение ldap.INVALID_CREDENTIALS без необходимости импортировать исключение в файл testcase?
  5. Должен ли я использовать patch.object() вместо этого, и если да, то как?

Спасибо.

+1

1-3) Кажется, хорошо для меня ... 4) 'импорта ldap' вместо и установить' side_effect = ldap.INVALID_CREDENTIALS'? – Chris

+0

Вы всегда можете сделать тот же тест, но с более простыми объектами, сделанными вами самим ... – shackra

ответ

36

См: 26.5.3.4. Applying the same patch to every test method

Это имеет смысл настроить патчер таким образом на загрузочном если вы хотите латание сделать для всех методов испытаний.

+2

То же самое для pre-Python3 Mock (размещен на http://www.voidspace.org.uk/python/mock/), см. [Применение одного и того же патча для каждого метода тестирования] (http://www.voidspace.org.uk/python/mock/examples.html#applying-the-same-patch-to-every-test-method). – musiphil

+2

Я просто столкнулся с проблемой, когда у меня был класс на уровне класса TestCase, и предположил, что он уже будет на месте при вызове метода 'setUp()'. ЭТО НЕ ТОТ СЛУЧАЙ; класс-класс mocks не применяются во времени для использования в 'setUp()'. Я решил проблему, вместо этого, создав вспомогательный метод, который я использую во всех своих тестах. Не уверен, что это лучший подход, но он работает. – berto

+0

@berto Если вы расширите свой комментарий в ответе, я думаю, это будет полезно. Это другое и, вероятно, более легкое решение, чем другие. – KobeJohn

4

Если у вас есть много исправлений, чтобы применить, и вы хотите их применить к вещам инициализированных в методах нАлАдкИ тоже попробовать это:

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

считают использование 'patch.stop_all()' в 'tearDown()'. –

+0

Я попробовал это - кажется, что apply_patches также нужно запустить. Рассмотрим строку типа '' для патча в self.applied_patches: patch.start() ' – F1Rumors

3

Я начну отвечать на ваши вопросы, а потом я дам подробный пример взаимодействия patch() и setUp().

  1. Не думаю, что это выглядит правильно, см. Ответ # 3 для деталей.
  2. Да, фактический вызов патча выглядит так, будто он должен высмеять требуемый объект.
  3. Нет, вы почти никогда не хотите использовать декоратор @patch() на setUp(). Вам повезло, потому что объект создан в setUp() и никогда не создается во время тестового метода.
  4. Я не знаю, как сделать объект-макет, создающий исключение без импорта этого исключения в файл тестового примера.
  5. Я не вижу необходимости в patch.object() здесь. Он просто позволяет вам патч атрибутов объекта вместо указания цели в виде строки.

Чтобы расширить ответ №3, проблема заключается в том, что декоратор patch() применяется только во время работы декорированной функции. Как только setUp() вернется, патч будет удален.В вашем случае это работает, но я уверен, что это смутит кого-то, смотрящего на этот тест. Если вы действительно хотите, чтобы патч произошел во время setUp(), я бы предложил использовать оператор with, чтобы было очевидно, что патч будет удален.

В следующем примере представлены два тестовых примера. TestPatchAsDecorator показывает, что при декорировании класса применяется патч во время тестового метода, но не во время setUp(). TestPatchInSetUp показывает, как вы можете применить патч так, чтобы он был установлен во время как setUp(), так и метода тестирования. Вызов self.addCleanUp() гарантирует, что патч будет удален во время 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) 
Смежные вопросы