因为我们的测试覆盖了?RemovalService ,因此我们不会对我们测试用例中?UploadService ?的内部函数?rm ?进行验证.相反,我们将调用?UploadService ?的?RemovalService.rm ?方法来进行简单测试(当然没有其他副作用),我们通过之前的测试用例便能知道它可以正确地工作.
这里有两种方法来实现测试:
- 模拟 RemovalService.rm 方法本身.
- 在 UploadService 的构造函数中提供一个模拟实例.
因为这两种方法都是单元测试中非常重要的方法,所以我们将同时对这两种方法进行回顾.
方法 1:模拟实例的方法
mock ?库有一个特殊的方法装饰器,可以模拟对象实例的方法和属性,即?@mock.patch.object decorator ?装饰器:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from mymodule import RemovalService, UploadService
import mock
import unittest
class RemovalServiceTestCase(unittest.TestCase):
? ?@mock.patch('mymodule.os.path')
? ?@mock.patch('mymodule.os')
? ?def test_rm(self, "Failed to not remove the file if not present.")
? ? ? ?# make the file 'exist'
? ? ? ?mock_path.isfile.return_value = True
? ? ? ?reference.rm("any path")
? ? ? ?mock_os.remove.assert_called_with("any path")
class UploadServiceTestCase(unittest.TestCase):
? ?@mock.patch.object(RemovalService, 'rm')
? ?def test_upload_complete(self, mock_rm):
? ? ? ?# build our dependencies
? ? ? ?removal_service = RemovalService()
? ? ? ?reference = UploadService(removal_service)
? ? ? ?# call upload_complete, which should, in turn, call `rm`:
? ? ? ?reference.upload_complete("my uploaded file")
? ? ? ?# check that it called the rm method of any RemovalService
? ? ? ?mock_rm.assert_called_with("my uploaded file")
? ? ? ?# check that it called the rm method of _our_ removal_service
? ? ? ?removal_service.rm.assert_called_with("my uploaded file")
非常棒!我们验证了?UploadService ?成功调用了我们实例的?rm ?方法.你是否注意到一些有趣的地方?这种修补机制(patching mechanism)实际上替换了我们测试用例中的所有?RemovalService ?实例的?rm ?方法.这意味着我们可以检查实例本身.如果你想要了解更多,可以试着在你模拟的代码下断点,以对这种修补机制的原理获得更好的认识.
陷阱:装饰顺序
当我们在测试方法中使用多个装饰器,其顺序是很重要的,并且很容易混乱.基本上,当装饰器被映射到方法参数时,装饰器的工作顺序是反向的[3].思考这个例子:
? ?@mock.patch('mymodule.sys')
? ?@mock.patch('mymodule.os')
? ?@mock.patch('mymodule.os.path')
? ?def test_something(self, mock_os_path, mock_sys):
? ? ? ?pass
注意到我们的参数和装饰器的顺序是反向匹配了吗?这部分是由?Python 的工作方式[4]所导致的.这里是使用多个装饰器的情况下它们执行顺序的伪代码:
patch_sys(patch_os(patch_os_path(test_something)))
因为?sys ?补丁位于最外层,所以它最晚执行,使得它成为实际测试方法参数的最后一个参数.请特别注意这一点,并且在运行你的测试用例时,使用调试器来保证正确的参数以正确的顺序注入.
方法 2:创建 Mock 实例
我们可以使用构造函数为?UploadService ?提供一个 Mock 实例,而不是模拟特定的实例方法.我更推荐方法 1,因为它更加精确,但在多数情况,方法 2 或许更加有效和必要.让我们再次重构测试用例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from mymodule import RemovalService, "Failed to not remove the file if not present.")
? ? ? ?# make the file 'exist'
? ? ? ?mock_path.isfile.return_value = True
? ? ? ?reference.rm("any path")
? ? ? ?mock_os.remove.assert_called_with("any path")
class UploadServiceTestCase(unittest.TestCase):
? ?def test_upload_complete(self, mock_rm):
? ? ? ?# build our dependencies
? ? ? ?mock_removal_service = mock.create_autospec(RemovalService)
? ? ? ?reference = UploadService(mock_removal_service)
? ? ? ?# call upload_complete, call `rm`:
? ? ? ?reference.upload_complete("my uploaded file")
? ? ? ?# test that it called the rm method
? ? ? ?mock_removal_service.rm.assert_called_with("my uploaded file")
在这个例子中,我们甚至不需要修补任何功能,只需为?RemovalService ?类创建一个 auto-spec,然后将实例注入到我们的?UploadService ?以验证功能.
(编辑:ASP站长网)
|