TDDを学習する私の試みでは、単体テストを学習しようとし、Pythonでモックを使用しています。ゆっくりと慣れてきましたが、これを正しく行っているかどうかはわかりません。警告:ベンダー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
いくつか質問があります:
ありがとう。
patch()
は、関数デコレータとしてだけでなく、クラスデコレータとしても使用できます。その後、以前のようにモック関数を渡すことができます。
@patch('mymodule.SomeClass')
class MyTest(TestCase):
def test_one(self, MockSomeClass):
self.assertIs(mymodule.SomeClass, MockSomeClass)
参照: 26.5.3.4。すべてのテストメソッドに同じパッチを適用する (代替方法もリストされています)
すべてのテストメソッドに対してパッチを適用する場合は、setUpでこの方法でパッチを設定する方が合理的です。
多くのパッチを適用し、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()
最初に質問に答えてから、patch()
とsetUp()
がどのように相互作用するかの詳細な例を示します。
@patch()
でsetUp()
デコレータを使用することはほとんどありません。オブジェクトはsetUp()
で作成され、テストメソッド中に作成されることはないため、ラッキーになりました。patch.object()
の必要性は見当たりません。ターゲットを文字列として指定する代わりに、オブジェクトの属性にパッチを適用するだけです。質問#3への私の答えを拡張すると、問題は、patch()
デコレーターが装飾された関数の実行中にのみ適用されることです。 setUp()
が戻るとすぐに、パッチは削除されます。あなたの場合、それは機能しますが、このテストを見ている人を混乱させるに違いないでしょう。 setUp()
の間にのみパッチを実行したい場合は、with
ステートメントを使用して、パッチが削除されることを明確にすることをお勧めします。
次の例には、2つのテストケースがあります。 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)
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
_
この場合、すべてのテストメソッドに2番目の引数MockSomeClass
を追加する必要がなくなり、多くのコードの繰り返しを節約できることに注意してください。
この説明は https://docs.python.org/3/library/unittest.mock.html#patch にあります。
patch()
がデコレーターとして使用され、newが省略された場合、作成されたモックは追加の引数として装飾された関数に渡されます。 。
上記の答えはすべて省略していますnewが、それを含めると便利です。
パッチを適用した内部関数を作成し、setUp
から呼び出すことができます。
元のsetUp
関数が次の場合:
def setUp(self):
some_work()
次に、次のように変更してパッチを適用できます。
def setUp(self):
@patch(...)
def mocked_func():
some_work()
mocked_func()