我有一个测试类,它有一个模拟装饰器和几个测试。每个测试都会收到模拟,因为模拟是在类级别定义的。伟大的。它看起来像这样:
@mock.patch("foo", bar)
class TestMyThing(TestCase):
def test_A(self):
assert something
def test_B(self):
assert something
def test_C(self):
assert something
def test_D(self):
assert something
我现在想要
test_D
获得一个为 foo
嘲笑的不同值。我首先尝试:
@mock.patch("foo", bar)
class TestMyThing(TestCase):
def test_A(self):
assert something
def test_B(self):
assert something
def test_C(self):
assert something
@mock.patch("foo", baz)
def test_D(self):
assert something
这行不通。目前,为了让单元测试采用装饰
mock.patch
的 test_D
,我必须删除装饰 class的
mock.patch
。这意味着创建大量 DRY 并执行以下操作:
class TestMyThing(TestCase):
@mock.patch("foo", bar)
def test_A(self):
assert something
@mock.patch("foo", bar)
def test_B(self):
assert something
@mock.patch("foo", bar)
def test_C(self):
assert something
@mock.patch("foo", baz)
def test_D(self):
assert something
由于 DRY 样板,这是不理想的,这使得它容易出错并且违反开闭原则。有没有更好的方法来实现相同的逻辑?
是的!您可以利用 setUp
的
tearDown
/unittest.TestCase
方法,以及“纯”形式的 unittest.mock.patch
(即不作为上下文管理器或装饰器)返回一个“修补程序”对象,该对象有 start
/stop
方法来控制它何时应该发挥其魔力。
您可以调用修补程序在
setUp
内启动并在 tearDown
内停止,如果您在测试用例的属性中保留对它的引用,则可以轻松地在选定的测试方法中手动停止它。这是一个完整的工作示例:
from unittest import TestCase
from unittest.mock import patch
class Foo:
@staticmethod
def bar() -> int:
return 1
class TestMyThing(TestCase):
def setUp(self) -> None:
self.foo_bar_patcher = patch.object(Foo, "bar", return_value=42)
self.mock_foo_bar = self.foo_bar_patcher.start()
super().setUp()
def tearDown(self) -> None:
self.foo_bar_patcher.stop()
super().tearDown()
def test_a(self):
self.assertEqual(42, Foo.bar())
def test_b(self):
self.assertEqual(42, Foo.bar())
def test_c(self):
self.assertEqual(42, Foo.bar())
def test_d(self):
self.foo_bar_patcher.stop()
self.assertEqual(1, Foo.bar())
无论有不同的变体,例如
patch.object
(我在这里使用)、patch.multiple
等,这种修补行为都是相同的。
请注意,对于此示例,无需在属性中保留对修补程序生成的实际
MagicMock
实例的引用,就像我对 mock_foo_bar
所做的那样。我通常这样做是因为我经常想在测试方法中针对模拟实例检查某些内容。
值得一提的是,您也可以为此使用
setUpClass
/tearDownClass
,但是如果您停止它,则需要小心重新启动补丁,因为这些方法是(顾名思义)每个测试用例仅调用一次,而 setUp
/tearDown
在每个测试方法之前/之后调用一次。
PS:
setUp
/tearDown
在TestCase
上的默认实现什么也不做,但在我看来,养成调用超类方法的习惯仍然是一个很好的做法,除非你故意想省略该调用。
虽然接受的答案非常好,但我会选择为类保留装饰器并为要覆盖模拟的方法使用上下文管理器的方法,请参阅下面的示例代码:
@mock.patch("foo", bar)
class TestMyThing(TestCase):
def test_A(self):
assert something
def test_B(self):
assert something
def test_C(self):
assert something
def test_D(self):
with patch('foo', baz):
assert something
这实现了您的目标,因为上下文管理器模拟应用在测试函数内部,因此它发生在应用类装饰器模拟之后,因此它有效地覆盖了类装饰器模拟。另外,我认为它更简单并且需要更少的代码,特别是如果您没有其他原因来实现
setUp()
和 tearDown()
方法。