ProgramingTip

unittest setUp으로 mock을 사용하는 방법

bestdevel 2021. 1. 7. 21:27
반응형

unittest setUp으로 mock을 사용하는 방법


TDD를 배우려는 시도에서 단위 테스트를 배우고로 mock을 사용합니다. 천천히 확실하게 알고 있는지 확인합니다. 미리 경고 : 공급 업체 API가 미리 준비된 2.4 pyc 파일로 제공되기 때문에 python 2.4를 사용하고 있습니다. 그래서 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

몇 가지 질문으로 연결됩니다.

  1. 그게 맞나요? :)
  2. 내가 테스트하는 클래스 내에서 인스턴스화되는 클래스를 시도하고 조롱하는 적절한 방법입니까?
  3. setUp에서 @patch 데코레이터를 호출해도 괜찮습니까? 아니면 이상한 부작용이 발생 했습니까?
  4. 어떤 경우에도 내 테스트 케이스 파일로 예외를 필요없이 ldap.INVALID_CREDENTIALS 예외를 발생시키기 위해 모의 사용할 수 있습니까?
  5. 대신 patch.object ()를 사용하며 어떻게해야합니까?

감사합니다.


patch()함수 데코레이터 프리미엄 클래스 데코레이터로 사용할 수 있습니다 . 그런 다음 이전과 같이 mocked 함수를 사용할 수 있습니다.

@patch('mymodule.SomeClass')
class MyTest(TestCase):

    def test_one(self, MockSomeClass):
        self.assertIs(mymodule.SomeClass, MockSomeClass)

참조 : 26.5.3.4. 모든 테스트 방법에 동일한 패치 적용 (대안 사항도 모든 테스트 )

모든 테스트 방법은 패치를 수행하는 방식으로 패치 프로그램을 설정하는 것이 더 합리적입니다.


나는 당신의 질문에 답하여 다음 나는 방법에 대한 자세한 예를주지 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)

적용 할 패치가 많고 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.stopall()

new인수가 patch()데코레이터 에게 전달 되는 허용되는 답변의 변형을 지적하고 싶습니다 .

from unittest.mock import patch, Mock

MockSomeClass = Mock()

@patch('mymodule.SomeClass', new=MockSomeClass)
class MyTest(TestCase):
    def test_one(self):
        # Do your test here

이 경우 더 이상 MockSomeClass모든 테스트 메서드에 두 번째 인수를 추가 할 필요가 없으므로 많은 코드 반복을 줄일 수 있습니다.

이에 대한 설명은 https://docs.python.org/3/library/unittest.mock.html#patch 에서 찾을 수 있습니다 .

경우 patch()데코레이터로 사용되는 새로운 생략, 생성 된 모의은 장식 기능을 추가 인수로 전달됩니다.

무엇보다도 new를 생략 했지만 포함하는 것이 편리 할 수 ​​있습니다.


패치 된 내부 함수를 만들고에서 호출 할 수 있습니다 setUp.

원래 setUp기능이 다음과 같은 경우 :

def setUp(self):
    some_work()

그런 다음 다음으로 변경하여 패치 할 수 있습니다.

def setUp(self):
    @patch(...)
    def mocked_func():
        some_work()

    mocked_func()

참조 URL : https://stackoverflow.com/questions/15821465/how-to-properly-use-mock-in-python-with-unittest-setup

반응형