我在Python中遇到了一个单元测试问题。具体来说,当我尝试模拟代码导入的函数时,分配给该函数输出的变量会被分配给MagicMock对象,而不是模拟函数的return_value。我一直在为python的单元测试库挖掘文档,但运气不好。
以下是我想要测试的代码:
from production_class import function_A, function_B, function_M
class MyClass:
def do_something(self):
variable = functionB()
if variable:
do_other_stuff()
else:
do_something_else
这就是我尝试过的:
@mock.patch(path.to.MyClass.functionB)
@mock.patch(<other dependencies in MyClass>)
def test_do_something(self, functionB_mock):
functionB_mock.return_value = None # or False, or 'foo' or whatever.
myClass = MyClass()
myClass.do_something()
self.assertTrue(else_block_was_executed)
我遇到的问题是,当测试在MyClass中达到variable = functionB
时,变量没有设置为我的返回值;它被设置为一个MagicMock对象(因此if语句的求值结果总是为True(。如何模拟导入的函数,以便在执行时,变量实际上被设置为返回值,而不是MagicMock对象本身?
我们必须了解path.to.MyClass.functionB
实际使用的导入路径。当模拟对象时,不一定直接使用对象所在的路径,而是在递归导入模块时,interpreter看到的路径。
例如,如果测试从myclass.py
导入MyClass
,而该文件从production_class.py
导入functionB
,则模拟路径将是myclass.functionB
,而不是production_class.functionB
。
还有一个问题是,您需要在中添加MyClass.do_other_stuff
和MyClass.do_something_else
的mock,以检查MyClass
是否根据functionB
的返回值调用了正确的下游方法。
下面是一个工作示例,它测试functionB
的两个可能返回值,以及它们是否调用了正确的下游方法:
myclass.py
from production_class import functionA, functionB, functionM
class MyClass:
def do_something(self):
variable = functionB()
if variable:
self.do_other_stuff()
else:
self.do_something_else()
def do_other_stuff(self):
pass
def do_something_else(self):
pass
production_class.py
import random
def functionA():
pass
def functionB():
return random.choice([True, False])
def functionM():
pass
test_myclass.py
import unittest
from unittest.mock import patch
from myclass import MyClass
class MyTest(unittest.TestCase):
@patch('myclass.functionB')
@patch('myclass.MyClass.do_something_else')
def test_do_something_calls_do_something_else(self, do_something_else_mock, functionB_mock):
functionB_mock.return_value = False
instance = MyClass()
instance.do_something()
do_something_else_mock.assert_called()
@patch('myclass.functionB')
@patch('myclass.MyClass.do_other_stuff')
def test_do_something_calls_do_other_stuff(self, do_other_stuff_mock, functionB_mock):
functionB_mock.return_value = True
instance = MyClass()
instance.do_something()
do_other_stuff_mock.assert_called()
if __name__ == '__main__':
unittest.main()
调用python test_myclass.py
导致:
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
我最终做的是更改MyClass中的导入语句,以导入对象而不是单个方法。然后我就可以毫不费力地模仿这个物体了。
更明确地说,我把MyClass改成了这样:
import production_class as production_class
class MyClass:
def do_something(self):
variable = production_class.functionB()
if variable:
do_other_stuff()
else:
do_something_else
并将我的测试改为
@mock.patch(path.to.MyClass.production_class)
def test_do_something(self, prod_class_mock):
prod_class_mock.functionB.return_value = None
myClass = MyClass()
myClass.do_something()
self.assertTrue(else_block_was_executed)