파이썬 모의 클래스 속성

Salman Mehmood 2023년6월21일
  1. 클래스 속성을 조롱하는 이유
  2. 클래스 속성을 조롱하는 가능한 솔루션
  3. 파이썬 모의 클래스 생성자
파이썬 모의 클래스 속성

이 문서의 주요 목표는 테스트 및 디버깅 목적으로 Python 단위 테스트 모듈인 unittest를 사용하여 클래스 특성을 조작하는 방법을 보여 주는 것입니다.

클래스 속성을 조롱하는 이유

버그, 오류 및 코너 케이스에 대해 개발된 코드를 테스트하는 것은 주로 응용 프로그램이 여러 사용자를 대상으로 하는 경우 응용 프로그램을 개발할 때 가장 중요한 측면 중 하나입니다.

내장된 Python 모듈 unittest를 사용하여 테스트 사례를 수행하여 코드의 무결성을 테스트할 수 있습니다. 엄격한 테스트가 필요한 가장 일반적인 요소 중 하나는 클래스 특성입니다.

클래스 속성은 예기치 않은 동작을 방지하기 위해 무작위 입력을 처리할 수 있습니다.

다음 코드를 고려하십시오.

class Calculate:
    value = 22  # Needs to be tested for different data types

    def Process(self):  # An example method
        pass

이라는 속성과 프로세스라는 메소드를 포함하는 계산이라는 클래스를 고려하십시오. 사전은 내부에 저장되며 나중에 요구 사항 및 데이터 유형에 따라 처리됩니다.

속성이 거의 모든 유형의 사전을 저장할 수 있고 오류 없이 처리되도록 하려면 속성을 테스트하여 구현에 오류가 없고 수정이 필요하지 않은지 확인해야 합니다.

클래스 속성을 조롱하는 가능한 솔루션

두 가지 방법으로 클래스 속성을 조롱할 수 있습니다. PropertyMock을 사용하고 PropertyMock을 사용하지 않습니다. 예제 코드를 사용하여 아래에서 각각 알아봅시다.

PropertyMock을 사용하여 클래스 속성 모의

속성을 모의하기 위해 주로 속성의 모의 또는 클래스의 설명자로 사용되는 PropertyMock을 사용할 수 있습니다.

PropertyMock은 속성을 가져온 후 속성의 반환 값을 변경하기 위해 __get____set__ 메서드를 제공한다는 점은 주목할 가치가 있습니다.

다음 코드를 고려하십시오.

import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, PropertyMock


class Calculate:
    value = 22  # Needs to be tested for different data types

    def Process(self):  # An example method
        pass


class Test(unittest.TestCase):
    @patch.object(Calculate, "value", new_callable=PropertyMock)
    def test_method(self, mocked_attrib):
        mocked_attrib.return_value = 1
        # Will pass without any complaints
        self.assertEqual(Calculate().value, 1)
        print("First Assertion Passed!")

        self.assertEqual(Calculate().value, 22)  # Will throw an assertion
        print("Second Assertion Passed!")


if __name__ == "__main__":
    Test().test_method()

출력:

First Assertion Passed!
Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 24, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 20, in test_method
    self.assertEqual(Calculate().value, 22) # Will throw an assertion 
  File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: 1 != 22

솔루션에서 Calculate.value의 값을 수정하기 위해 test_method라는 새 메서드가 생성됩니다. 함수는 patch.object로 데코레이트된다는 점에 유의해야 합니다.

모듈의 patch 데코레이터는 모듈 및 클래스 수준 속성을 패치하는 데 도움이 됩니다. 패치 대상을 좀 더 구체적으로 만들기 위해 patch 대신 patch.object를 사용하여 메서드를 직접 패치합니다.

데코레이터에서 먼저 Calculate라는 클래스 이름이 전달되어 패치할 개체가 전달되는 속성 value의 이름이 있는 Calculate의 일부임을 나타냅니다.

마지막 매개변수는 PropertyMock 개체로, 다른 숫자를 전달하여 value 속성을 덮어씁니다.

프로그램의 일반적인 흐름은 다음과 같습니다.

  1. test_method가 호출됩니다.
  2. Calculate 클래스의 valuePropertyMock 인스턴스에 할당된 new_callable로 패치됩니다.
  3. return_value 속성을 사용하여 Calculate.value1로 덮어씁니다.
  4. 첫 번째 어설션이 확인됩니다. 여기서 Calculate.value1과 같아야 합니다.
  5. 두 번째 어설션이 확인됩니다. 여기서 Calculate.value22와 같아야 합니다.

PropertyMock을 사용하지 않고 클래스 속성 모의

PropertyMock을 사용하지 않고도 해결할 수 있습니다. 위의 예를 약간만 수정하면 됩니다.

다음 코드를 고려하십시오.

import unittest
from unittest.mock import patch


class Calculate:
    value = 22  # Needs to be tested for different data types

    def Process(self):  # An example method
        pass


class Test(unittest.TestCase):
    @patch.object(Calculate, "value", 1)
    def test_method(self):
        # Will pass without any complaints
        self.assertEqual(Calculate().value, 1)
        print("First Assertion Passed!")

        # Will throw an assertion because "Calculate.value" is now 1
        self.assertEqual(Calculate().value, 22)
        print("Second Assertion Passed!")


if __name__ == "__main__":
    Test().test_method()

출력:

First Assertion Passed!
Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 23, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 19, in test_method
    self.assertEqual(Calculate().value, 22) # Will throw an assertion because "Calculate.value" is now 1
  File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: 1 != 22

PropertyMock의 인스턴스를 new_callable에 전달하는 대신 Calculate.value에 저장하려는 값을 직접 제공할 수 있습니다.

파이썬 모의 클래스 생성자

초기화된 모든 변수가 의도한 대로 작동하고 의도하지 않은 동작을 나타내지 않는지 확인합니다. 코너 케이스를 줄이기 위해 다양한 입력으로 생성자를 테스트하는 것도 필요합니다.

다음 코드를 고려하십시오.

class Calculate:
    num = 0
    dic = {}

    def __init__(self, number, dictt):
        self.num = number
        self.dic = dictt

    def Process(self):  # An example method
        pass

Calculate 클래스의 생성자를 테스트하고 싶다고 가정해 보겠습니다. 속성이 의도한 대로 작동하도록 하려면 생성자를 패치하고 가능한 오류를 제거하기 위해 다양한 입력과 함께 전달해야 합니다.

어떻게 할 수 있습니까? 다음 솔루션을 참조하십시오.

patch.object 데코레이터를 사용하여 생성자 패치

patch.object 데코레이터를 사용하여 생성자를 패치할 수 있습니다.

import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, PropertyMock


class Calculate:
    num = 0
    dic = {}

    def __init__(self):
        self.num = 1
        self.dic = {"11", 2}

    def Process(self):  # An example method
        pass


class Test(unittest.TestCase):
    @patch.object(Calculate, "__new__")
    def test_method(self, mocked_calc):
        mocked_instance = MagicMock()
        mocked_instance.num = 10
        mocked_instance.dic = {"22": 3}
        mocked_calc.return_value = mocked_instance

        self.assertEqual(Calculate().num, 10)
        self.assertEqual(Calculate().dic, {"22": 3})

        print("First set of Asserts done!")

        self.assertEqual(Calculate().num, 1)
        self.assertEqual(Calculate().dic, {"11", 2})
        print("Second set of Asserts done!")


if __name__ == "__main__":
    Test().test_method()

출력:

The first set of Asserts is done!
Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 37, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 32, in test_method
    self.assertEqual(Calculate().num, 1)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: 10 != 1

__init__을 패치하는 대신 __new__를 패치했다는 점은 주목할 가치가 있습니다.

클래스의 인스턴스는 __new__가 실행될 때 생성되지만 __init__에서는 변수만 초기화되기 때문입니다. 그래서 새로운 모의 인스턴스를 만들어야 하므로 __init__ 대신 __new__를 패치해야 하는 이유는 무엇입니까?

Salman Mehmood avatar Salman Mehmood avatar

Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.

LinkedIn

관련 문장 - Python Unit Test