Python モック クラス属性
この記事の主な目的は、テストとデバッグの目的で、python 単体テスト モジュール unittest
を使用してクラス属性を操作する方法を示すことです。
クラス属性をモックする理由
開発したコードのバグ、エラー、およびコーナー ケースをテストすることは、アプリケーションを開発する際の最も重要な側面の 1つです。主にアプリケーションが複数のユーザーを対象としている場合です。
組み込みの Python モジュール unittest
を使用して、テスト ケースを実行し、コードの整合性をテストできます。 厳密なテストを必要とする最も一般的な要素の 1つは、クラス属性です。
クラス属性は、予期しない動作を防ぐためにランダムな入力を処理できます。
次のコードを検討してください。
class Calculate:
value = 22 # Needs to be tested for different data types
def Process(self): # An example method
pass
value
という属性と Process
という名前のメソッドを含む Calculate
という名前のクラスを考えてみましょう。 ディクショナリは value
内に保存され、後で要件とデータ型に基づいて処理されます。
属性がほぼすべてのタイプのディクショナリを格納でき、エラーなしで処理されることを確認するには、属性をテストして、実装にエラーがなく、改訂が必要ないことを確認する必要があります。
クラス属性をモックするための可能な解決策
クラス属性は 2つの方法でモックできます。 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
このソリューションでは、新しいメソッド test_method
が作成され、Calculate.value
の値が変更されます。 関数が patch.object
で装飾されていることに注意することが重要です。
モジュールの patch
デコレーターは、モジュールとクラスレベルの属性にパッチを当てるのに役立ちます。 何にパッチを当てるかをもう少し具体的にするために、patch
の代わりに patch.object
を使用してメソッドに直接パッチを当てます。
デコレーターでは、最初にクラス名 Calculate
が渡されます。これは、パッチが適用されるオブジェクトが Calculate
の一部であり、属性 value
の名前が渡されることを示します。
最後のパラメーターは PropertyMock
オブジェクトで、別の数値を渡すことで value
属性を上書きします。
プログラムの大まかな流れは次のとおりです。
test_method
が呼び出されます。- クラス
Calculate
のvalue
がパッチされ、new_callable
が割り当てられ、PropertyMock
のインスタンスが割り当てられます。 Calculate.value
はreturn_value
プロパティを使用して1
で上書きされます。- 最初のアサーションがチェックされます。ここで、
Calculate.value
は1
に等しくなければなりません。 - 2 番目のアサーションがチェックされます。ここで、
Calculate.value
は22
に等しくなければなりません。
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
に渡すことができます。
Python モック クラス コンストラクター
初期化されたすべての変数が意図したとおりに機能し、意図しない動作を示さないことを確認してください。 コーナーケースを減らすために、さまざまな入力でコンストラクターをテストすることも必要です。
次のコードを検討してください。
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__
にパッチを当てるのでしょうか?
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