Подтвердить что ты не робот

Как правильно использовать mock в python с unittest setUp

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

Учитывая этот пример кода в '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

Отвечает мне на пару вопросов:

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

Спасибо.

4b9b3361

Ответ 2

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

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() и setUp().

  • Я не думаю, что это выглядит правильно, см. ответ №3 для деталей.
  • Да, фактический вызов патча выглядит так, будто он должен высмеять желаемый объект.
  • Нет, вы почти никогда не хотите использовать декоратор @patch() на setUp(). Вам повезло, потому что объект создан в setUp() и никогда не создается во время тестового метода.
  • Я не знаю, как сделать объект-макет, создающий исключение, без импорта этого исключения в файл тестового файла.
  • Здесь я не вижу необходимости 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)