副标题[/!--empirenews.page--]
《Mock在Python单元测试中的使用》要点: 本文介绍了Mock在Python单元测试中的使用,希望对您有用。如果有疑问,可以联系我们。
本文讲述的是 Python 中 Mock 的使用.
如何执行单元测试而不用考验你的耐心
很多时候,我们编写的软件会直接与那些被标记为“垃圾”的服务交互.用外行人的话说:服务对我们的应用程序很重要,但是我们想要的是交互,而不是那些不想要的副作用,这里的“不想要”是在自动化测试运行的语境中说的.例如:我们正在写一个社交 app,并且想要测试一下 “发布到 Facebook” 的新功能,但是不想每次运行测试集的时候真的发布到 Facebook.
Python 的?unittest ?库包含了一个名为?unittest.mock ?或者可以称之为依赖的子包,简称为?mock ?—— 其提供了极其强大和有用的方法,通过它们可以模拟并去除那些我们不希望的副作用.
注意:mock最近被收录[1]到了Python3.3的标准库中;先前发布的版本必须通过PyPI[2]下载Mock库.
恐惧系统调用
再举另一个例子,我们在接下来的部分都会用到它,这是就是系统调用.不难发现,这些系统调用都是主要的模拟对象:无论你是正在写一个可以弹出 CD 驱动器的脚本,还是一个用来删除 /tmp 下过期的缓存文件的 Web 服务,或者一个绑定到 TCP 端口的 socket 服务器,这些调用都是在你的单元测试上下文中不希望产生的副作用.
作为一个开发者,你需要更关心你的库是否成功地调用了一个可以弹出 CD 的系统函数(使用了正确的参数等等),而不是切身经历 CD 托盘每次在测试执行的时候都打开了.(或者更糟糕的是,弹出了很多次,在一个单元测试运行期间多个测试都引用了弹出代码!)
同样,保持单元测试的效率和性能意味着需要让如此多的“缓慢代码”远离自动测试,比如文件系统和网络访问.
对于第一个例子来说,我们要从原始形式换成使用?mock ?重构一个标准 Python 测试用例.我们会演示如何使用 mock 写一个测试用例,使我们的测试更加智能、快速,并展示更多关于我们软件的工作原理.
一个简单的删除函数
我们都有过需要从文件系统中一遍又一遍的删除文件的时候,因此,让我们在 Python 中写一个可以使我们的脚本更加轻易完成此功能的函数.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
def rm(filename):
? ?os.remove(filename)
很明显,我们的?rm ?方法此时无法提供比?os.remove ?方法更多的相关功能,但我们可以在这里添加更多的功能,使我们的基础代码逐步改善.
让我们写一个传统的测试用例,即,没有使用?mock :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from mymodule import rm
import os.path
import tempfile
import unittest
class RmTestCase(unittest.TestCase):
? ?tmpfilepath = os.path.join(tempfile.gettempdir(), "tmp-testfile")
? ?def setUp(self):
? ? ? ?with open(self.tmpfilepath, "wb") as f:
? ? ? ? ? ?f.write("Delete me!")
? ?def test_rm(self):
? ? ? ?# remove the file
? ? ? ?rm(self.tmpfilepath)
? ? ? ?# test that it was actually removed
? ? ? ?self.assertFalse(os.path.isfile(self.tmpfilepath), "Failed to remove the file.")
我们的测试用例相当简单,但是在它每次运行的时候,它都会创建一个临时文件并且随后删除.此外,我们没有办法测试我们的?rm ?方法是否正确地将我们的参数向下传递给?os.remove ?调用.我们可以基于以上的测试认为它做到了,但还有很多需要改进的地方.
使用 Mock 重构
让我们使用 mock 重构我们的测试用例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from mymodule import rm
import mock
import unittest
class RmTestCase(unittest.TestCase):
? ?@mock.patch('mymodule.os')
? ?def test_rm(self, mock_os):
? ? ? ?rm("any path")
? ? ? ?# test that rm called os.remove with the right parameters
? ? ? ?mock_os.remove.assert_called_with("any path")
使用这些重构,我们从根本上改变了测试用例的操作方式.现在,我们有一个可以用于验证其他功能的内部对象.
潜在陷阱
第一件需要注意的事情就是,我们使用了?mock.patch ?方法装饰器,用于模拟位于?mymodule.os ?的对象,并且将 mock 注入到我们的测试用例方法.那么只是模拟?os ?本身,而不是?mymodule.os ?下?os ?的引用(LCTT 译注:注意?@mock.patch('mymodule.os') ?便是模拟?mymodule.os ?下的?os ),会不会更有意义呢?
当然,当涉及到导入和管理模块,Python 的用法就像蛇一样灵活.在运行时,mymodule ?模块有它自己的被导入到本模块局部作用域的?os .因此,如果我们模拟?os ,我们是看不到 mock 在?mymodule ?模块中的模仿作用的.
这句话需要深刻地记住:
模拟一个东西要看它用在何处,而不是来自哪里.
如果你需要为?myproject.app.MyElaborateClass ?模拟?tempfile ?模块,你可能需要将 mock 用于myproject.app.tempfile ,而其他模块保持自己的导入.
先将那个陷阱放一边,让我们继续模拟.
向 ‘rm’ 中加入验证
(编辑:西双版纳站长网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|