向‘rm’中加入验证
之前定义的 rm 方法相当的简单 . 在盲目的删除之前,我们会拿它来验证一个路径是否存在,并验证其是否是一个文件. 让我们重构 rm 使其变得更加聪明:
#!/usr/bin/env Python# -*- coding: utf-8 -*-import osimport os.pathdef rm(filename):
if os.path.isfile(filename):
os.remove(filename)
很好. 现在,让我们调整我们的测试用例来保持测试的覆盖程度.
#!/usr/bin/env python# -*- coding: utf-8 -*-from mymodule import rmimport mockimport unittestclass RmTestCase(unittest.TestCase):
@mock.patch('mymodule.os.path')
@mock.patch('mymodule.os')
def test_rm(self, mock_os, mock_path):
# set up the mock
mock_path.isfile.return_value = False
rm("any path")
# test that the remove call was NOT called.
self.assertFalse(mock_os.remove.called, "Failed to not remove the file if not present.")
# make the file 'exist'
mock_path.isfile.return_value = True
rm("any path")
mock_os.remove.assert_called_with("any path")
我们的测试范例完全变化了. 现在我们可以核实并验证方法的内部功能是否有任何副作用.
将删除功能作为服务到目前为止,我们只是对函数功能提供模拟测试,并没对需要传递参数的对象和实例的方法进行模拟测试。接下来我们将介绍如何对对象的方法进行模拟测试。
首先,我们先将rm方法重构成一个服务类。实际上将这样一个简单的函数转换成一个对象并不需要做太多的调整,但它能够帮助我们了解mock的关键概念。下面是重构的代码:
#!/usr/bin/env python# -*- coding: utf-8 -*-import osimport os.pathclass RemovalService(object):
"""A service for removing objects from the filesystem."""
def rm(filename):
if os.path.isfile(filename):
os.remove(filename)
你可以发现我们的测试用例实际上没有做太多的改变:
#!/usr/bin/env python# -*- coding: utf-8 -*-from mymodule import RemovalServiceimport mockimport unittestclass RemovalServiceTestCase(unittest.TestCase):
@mock.patch('mymodule.os.path')
@mock.patch('mymodule.os')
def test_rm(self, mock_os, mock_path):
# instantiate our service
reference = RemovalService()
# set up the mock
mock_path.isfile.return_value = False
reference.rm("any path")
# test that the remove call was NOT called.
self.assertFalse(mock_os.remove.called, "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")
很好,RemovalService如同我们计划的一样工作。接下来让我们创建另一个以该对象为依赖项的服务:
#!/usr/bin/env python# -*- coding: utf-8 -*-import osimport os.pathclass RemovalService(object):
"""A service for removing objects from the filesystem."""
def rm(filename):
if os.path.isfile(filename):
os.remove(filename)
class UploadService(object):
def __init__(self, removal_service):
self.removal_service = removal_service
def upload_complete(filename):
self.removal_service.rm(filename)
到目前为止,我们的测试已经覆盖了RemovalService, 我们不会对我们测试用例中UploadService的内部函数rm进行验证。相反,我们将调用UploadService的RemovalService.rm方法来进行简单的测试(为了不产生其他副作用),我们通过之前的测试用例可以知道它可以正确地工作。
有两种方法可以实现以上需求:
模拟RemovalService.rm方法本身。
在UploadService类的构造函数中提供一个模拟实例。
因为这两种方法都是单元测试中非常重要的方法,所以我们将同时对这两种方法进行回顾。