加入收藏 | 设为首页 | 会员中心 | 我要投稿 西双版纳站长网 (https://www.0691zz.com.cn/)- 数据计算、IT业界、服务器、教程、云日志!
当前位置: 首页 > 服务器 > 安全 > 正文

Mock在Python单元测试中的使用

发布时间:2021-01-09 17:07:05 所属栏目:安全 来源:网络整理
导读:《Mock在Python单元测试中的使用》要点: 本文介绍了Mock在Python单元测试中的使用,希望对您有用。如果有疑问,可以联系我们。 本文讲述的是 Python 中 Mock 的使用. 如何执行单元测试而不用考验你的耐心 很多时候,我们编写的软件会直接与那些被标记为“垃
副标题[/!--empirenews.page--]

《Mock在Python单元测试中的使用》要点:
本文介绍了Mock在Python单元测试中的使用,希望对您有用。如果有疑问,可以联系我们。

本文讲述的是 Python 中 Mock 的使用.

如何执行单元测试而不用考验你的耐心

很多时候,我们编写的软件会直接与那些被标记为“垃圾”的服务交互.用外行人的话说:服务对我们的应用程序很重要,但是我们想要的是交互,而不是那些不想要的副作用,这里的“不想要”是在自动化测试运行的语境中说的.例如:我们正在写一个社交 app,并且想要测试一下 “发布到 Facebook” 的新功能,但是不想每次运行测试集的时候真的发布到 Facebook.

Python 的?unittest?库包含了一个名为?unittest.mock?或者可以称之为依赖的子包,简称为?mock?—— 其提供了极其强大和有用的方法,通过它们可以模拟mock并去除那些我们不希望的副作用.

注意:mock最近被收录[1]到了Python3.3的标准库中;先前发布的版本必须通过PyPI[2]下载Mock库.

恐惧系统调用

再举另一个例子,我们在接下来的部分都会用到它,这是就是系统调用.不难发现,这些系统调用都是主要的模拟对象:无论你是正在写一个可以弹出 CD 驱动器的脚本,还是一个用来删除 /tmp 下过期的缓存文件的 Web 服务,或者一个绑定到 TCP 端口的 socket 服务器,这些调用都是在你的单元测试上下文中不希望产生的副作用.

作为一个开发者,你需要更关心你的库是否成功地调用了一个可以弹出 CD 的系统函数(使用了正确的参数等等),而不是切身经历 CD 托盘每次在测试执行的时候都打开了.(或者更糟糕的是,弹出了很多次,在一个单元测试运行期间多个测试都引用了弹出代码!)

同样,保持单元测试的效率和性能意味着需要让如此多的“缓慢代码”远离自动测试,比如文件系统和网络访问.

对于第一个例子来说,我们要从原始形式换成使用?mock?重构一个标准 Python 测试用例.我们会演示如何使用 mock 写一个测试用例,使我们的测试更加智能、快速,并展示更多关于我们软件的工作原理.

一个简单的删除函数

我们都有过需要从文件系统中一遍又一遍的删除文件的时候,因此,让我们在 Python 中写一个可以使我们的脚本更加轻易完成此功能的函数.

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import os
  4. def rm(filename):
  5. ? ?os.remove(filename)

很明显,我们的?rm?方法此时无法提供比?os.remove?方法更多的相关功能,但我们可以在这里添加更多的功能,使我们的基础代码逐步改善.

让我们写一个传统的测试用例,即,没有使用?mock

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from mymodule import rm
  4. import os.path
  5. import tempfile
  6. import unittest
  7. class RmTestCase(unittest.TestCase):
  8. ? ?tmpfilepath = os.path.join(tempfile.gettempdir(), "tmp-testfile")
  9. ? ?def setUp(self):
  10. ? ? ? ?with open(self.tmpfilepath, "wb") as f:
  11. ? ? ? ? ? ?f.write("Delete me!")
  12. ? ?def test_rm(self):
  13. ? ? ? ?# remove the file
  14. ? ? ? ?rm(self.tmpfilepath)
  15. ? ? ? ?# test that it was actually removed
  16. ? ? ? ?self.assertFalse(os.path.isfile(self.tmpfilepath), "Failed to remove the file.")

我们的测试用例相当简单,但是在它每次运行的时候,它都会创建一个临时文件并且随后删除.此外,我们没有办法测试我们的?rm?方法是否正确地将我们的参数向下传递给?os.remove?调用.我们可以基于以上的测试认为它做到了,但还有很多需要改进的地方.

使用 Mock 重构

让我们使用 mock 重构我们的测试用例:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from mymodule import rm
  4. import mock
  5. import unittest
  6. class RmTestCase(unittest.TestCase):
  7. ? ?@mock.patch('mymodule.os')
  8. ? ?def test_rm(self, mock_os):
  9. ? ? ? ?rm("any path")
  10. ? ? ? ?# test that rm called os.remove with the right parameters
  11. ? ? ? ?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’ 中加入验证

(编辑:西双版纳站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读