什么是单元测试?

编写测试来验证某一个模块的功能正确性, 一般会指定输出, 验证输出是否符合预期。

单元测试,就不得不提 Python unittest 库(更多参看文章结尾中的参考资料)它提供了我们需要的大多数工具。

例子:

import unittest
# 将要被测试的排序函数
def sort(arr):length = len(arr)for i in range(0, length):for j in range(i + 1, length):if arr[i] >= arr[j]:arr[i], arr[j] = arr[j], arr[i]
# 编写子类继承 unittest.TestCase
class TestSort(unittest.TestCase):# 以 test 开头的函数将会被测试def test_sort(self):arr = [3, 4, 1, 5, 6]sort(arr)# assert 结果跟我们期待的一样self.assertEqual(arr, [1, 3, 4, 5, 6])
if __name__ == '__main__':unittest.main()

单元测试.png

1、需要创建一个类 TestSort , 继承类 ‘unittest.TestCase’
2、在这个类中定义相应的测试函数 test_sort(), 进行测试。 注意, 测试函数要以 ‘test’ 开头。
3、测试函数的内部, 通常使用 assertEqual()、 assertTrue()、 assertFalse() 和 assertRaise() 等 assert 语句对结果进行验证。

如果在 IPython 或者 Jupyter 环境下使用:

unittest.main(argv=['first-arg-is-ignored'], exit=False)

组织TestSuite

使用TestSuite控制用例执行的顺序,添加到TestSuite中的case是会按照添加的顺序执行的。

import unittest
def add(a, b):return a+b
def minus(a, b):return a-b
def multi(a, b):return a*b
def divide(a, b):return a/b
class TestMathFunc(unittest.TestCase):def test_add(self):"""Test method add(a, b)"""self.assertEqual(3, add(1, 2))self.assertNotEqual(3, add(2, 2))def test_minus(self):"""Test method minus(a, b)"""self.assertEqual(1, minus(3, 2))def test_multi(self):"""Test method multi(a, b)"""self.assertEqual(6, multi(2, 3))def test_divide(self):"""Test method divide(a, b)"""self.assertEqual(2, divide(6, 3))self.assertEqual(2, divide(5, 2))
if __name__ == '__main__':suite = unittest.TestSuite()tests = [TestMathFunc("test_minus"), TestMathFunc("test_add"), TestMathFunc("test_divide")]suite.addTests(tests)# verbosity 参数可以控制输出的错误报告的详细程度,默认是 1,如果设为 0,则不输出每一用例的执行结果,# 如果设为 2,则输出详细的执行结果runner = unittest.TextTestRunner(verbosity=2)runner.run(suite)

执行了三个case,并且顺序是按照我们添加进suite的顺序执行的。

组织TestSuite.png

更多可参看 https://blog.csdn.net/huilan_same/article/details/52944782

单元测试的几个技巧

Python 单元测试的几个技巧, 分别是 mock、 side_effect 和 patch。这三者用法不一样, 但都是一个核心思想, 即用虚假的实现, 来替换掉被测试函数的一些依赖项, 让我们能把更多的精力放在需要被测试的功能上。
可以参看 https://docs.python.org/zh-cn/3/library/unittest.mock-examples.html

mock

mock 是单元测试中最核心重要的一环。 mock 的意思, 便是通过一个虚假对象, 来代替被测试函数或模块需要的对象。
比如要测试个后端 API 逻辑的功能性, 但一般后端 API 都依赖于数据库、文件系统、网络等。 这样, 你就需要通过 mock, 来创建一些虚假的数据库层、文件系统层、 网络层对象, 以便可以简单地对核心后端逻辑单元进行测试。
Python mock 则主要使用 mock 或者 MagicMock 对象,一个简单示例:

import unittest
from unittest.mock import MagicMock
class A(unittest.TestCase):def m1(self):val2 = self.m2()print('m1.val2=', val2)self.m3(val2)def m2(self):print('m2')passdef m3(self, val):print('m3.val=',val)passdef test_m1(self):a = A()# m2() 替换为一个返回具体数值的 valuea.m2 = MagicMock(return_value="custom_val")# m3() 替换为另一个 mock(空函数)a.m3 = MagicMock()a.m1()self.assertTrue(a.m2.called)  # 验证 m2 被 call 过a.m3.assert_called_with("custom_val")  # 验证 m3 被指定参数 call 过
if __name__ == '__main__':unittest.main(verbosity=2)

mock.png

定义了一个类的三个方法 m1()、 m2()、 m3()。 我们需要对 m1() 进行单元测试, 但是 m1() 取决于 m2() 和 m3()。 如果 m2() 和 m3() 的内部比较复杂, 就不能只是简单地调m1() 函数来进行测试, 可能需要解决很多依赖项的问题。
使用mock,可以把 m2() 替换为一个返回具体数值的 value, 把 m3() 替换为另一个 mock(空函数) 。这样, 测试 m1() 就很容易了, 我们可以测试 m1() 调用 m2(), 并且用 m2() 的返回值调用 m3()。由于已被替换m2(),m3()中的print均未输出。

Mock Side Effect

mock 的函数, 属性是可以根据不同的输入, 返回不同的数值, 而不只是一个 return_value。

比如下面这个示例, 测试的是输入参数是否为负数, 输入小于 0 则输出为 1 , 否则输出为 2。

from unittest.mock import MagicMock
def side_effect(arg):if arg < 0:return 1else:return 2
mock = MagicMock()
mock.side_effect = side_effect
print('mock(-1) = ', mock(-1))
print('mock(2) = ', mock(2))

Mock Side Effect

patch

patch, 给开发者提供了非常便利的函数 mock 方法。 它可以应用 Python 的 decoration 模式或是 context manager 概念, 快速自然地 mock 所需的函数。有些函数可能不属于你,你也不在意它的内部实现而只是想调用这个函数然后得到结果而已,这种时候就可以用 patch 方式来模拟。

有 2 个函数,其中一个 send_shell_cmd 是其他人写的,怎么做的不在乎。一个函数 check_cmd_response 用来检查send_shell_cmd 返回的结果,然后对自己写的 check_cmd_response 做单元测试。假如 send_shell_cmd 函数可能需要一个真实的 PC ,这需要设备,而且每次返回还可能与预期不符,比如设备无法连接,想检查的东西忘记配置所以取不回来等等,这些都会干扰我自己函数的行为,而且问题和自己函数无关,这种时候就可以用 mock 模拟 send_shell_cmd 函数而且把预期返回写到这个模拟过程中,这样每次都会正确处理。

import re
from unittest import TestCase, TestSuite, TextTestRunner
from unittest.mock import patch
class LinuxTool(object):def __init__(self):passdef send_shell_cmd(self):return "Response from send_shell_cmd function"def check_cmd_response(self):response = self.send_shell_cmd()print("response: {}".format(response))return re.search(r"mock_send_shell_cmd", response)
class TestLinuxTool(TestCase):def setUp(self):self.linux_tool = LinuxTool()def tearDown(self):pass@patch("__main__.LinuxTool.send_shell_cmd")def test_check_cmd_response(self, mock_send_shell_cmd):mock_send_shell_cmd.return_value = "Response from emulated mock_send_shell_cmd function"status = self.linux_tool.send_shell_cmd()print("check result: {}" .format(status))self.assertTrue(status)
if __name__ == '__main__':suite = TestSuite()tests = [TestLinuxTool("test_check_cmd_response")]suite.addTests(tests)runner = TextTestRunner(verbosity=2)runner.run(suite)

patch.png

@patch("__main__.LinuxTool.send_shell_cmd")
def test_check_cmd_response(self, mock_send_shell_cmd):mock_send_shell_cmd.return_value = "Response from emulated mock_send_shell_cmd function"
...

mock_send_shell_cmd替代 LinuxTool.send_shell_cmd函数本身的存在,可以像之前提到的 mock
object 一样, 设置 return_value 和 side_effect。

如果 patch 多个外部函数,那么调用遵循自下而上的规则,比如:

@patch("function_C")
@patch("function_B")
@patch("function_A")
def test_check_cmd_response(self, mock_function_A, mock_function_B, mock_function_C):mock_function_A.return_value = "Function A return"mock_function_B.return_value = "Function B return"mock_function_C.return_value = "Function C return"self.assertTrue(re.search("A", mock_function_A()))self.assertTrue(re.search("B", mock_function_B()))self.assertTrue(re.search("C", mock_function_C()))

注意官网说明 https://docs.python.org/zh-cn/3/library/unittest.mock-examples.html#mocking-classes

mock provides three convenient decorators for this: patch(), patch.object() and patch.dict(). patch takes a single string, of the form package.module.Class.attribute to specify the attribute you are patching. It also optionally takes a value that you want the attribute (or class or whatever) to be replaced with. 'patch.object' takes an object and the name of the attribute you would like patched, plus optionally the value to patch it with.

上面例子中,可以有如下写法(测试 python 版本 3.7.3)

@patch("__main__.LinuxTool.send_shell_cmd")
def test_check_cmd_response(self, mock_send_shell_cmd):mock_send_shell_cmd.return_value = "Response from emulated mock_send_shell_cmd function"
...
# Test 为文档名
@patch("Test.LinuxTool.send_shell_cmd")
def test_check_cmd_response(self, mock_send_shell_cmd):mock_send_shell_cmd.return_value = "Response from emulated mock_send_shell_cmd function"
...@patch.object(LinuxTool,"send_shell_cmd")
def test_check_cmd_response(self, mock_send_shell_cmd):mock_send_shell_cmd.return_value = "Response from emulated mock_send_shell_cmd function"
...

@patch.png

@patch.object.png

参考资料:

极客时间 Python核心技术与实战学习

Python核心技术与实战(极客时间)链接:
http://gk.link/a/103Sv

Python必会的单元测试框架 —— unittest:
https://blog.csdn.net/huilan_same/article/details/52944782

unittest--- 单元测试框架:
https://docs.python.org/zh-cn/3/library/unittest.html

unittest.mock 上手指南:
https://docs.python.org/zh-cn/3/library/unittest.mock-examples.html

Mock 模块使用说明:
https://www.jianshu.com/p/55e5a6863c3f


GitHub链接:
https://github.com/lichangke/LeetCode

知乎个人首页:
https://www.zhihu.com/people/lichangke/

简书个人首页:
https://www.jianshu.com/u/3e95c7555dc7

个人Blog:
https://lichangke.github.io/

欢迎大家来一起交流学习

[Python核心技术与实战学习] 18 单元测试unittest 库相关推荐

  1. 极客时间课程《Python核心技术与实战》课程练习实践

    GitHub - zwdnet/PythonPractice: 极客时间课程<Python核心技术与实战>课程练习实践.极客时间课程<Python核心技术与实战>课程练习实践. ...

  2. python自然语言处理实战学习——1

    python自然语言处理实战学习笔记1 自信人生两百年,会当击水三千里. 第一章 NLP基础 1.NLP的概念 NLP(natural language processing,自然语言处理)是计算机科 ...

  3. Elasticsearch核心技术与实战学习笔记 43 | 分页与遍历:From, Size, Search After Scroll API

    一 序 本文属于极客时间Elasticsearch核心技术与实战学习笔记系列. 二 分页 2.1 From / Size 默认情况下,查询按照相关度算分排序,返回前 10 条记录 容易理解的分页方案 ...

  4. Python爬虫入门实战学习笔记(一)

    (全部代码在最后,学自"我的IT私塾") 什么是爬虫 网络爬虫,是一种按照一定规则,自动抓取互联网信息的程序或者脚本.由于互联网数据的多样性和资源的有限性,根据用户需求定向抓取相关 ...

  5. 『Python核心技术与实战』pandas.DataFrame()函数介绍

    pandas.DataFrame()函数介绍! 文章目录 一. 创建DataFrame 1.1. numpy创建 1.2. 直接创建 1.3. 字典创建 1.4. Series和DataFrame 二 ...

  6. Redis核心技术与实战-学习笔记(二十九):Redis并发控制

    一.需要并发控制的原因 Redis不可避免的会遇到并发访问问题,比如多用户同时下单,就会对缓存在Redis中的商品库存并发更新,一旦有了并发操作,数据就会被修改,如果我们没有对并发写请求做好控制,就可 ...

  7. Redis核心技术与实战-学习笔记(二十六):缓存雪崩、击穿、穿透

    一.缓存雪崩 缓存雪崩:大量应用请求无法在Redis缓存中进行处理,应用请求频繁访问数据库,导致数据库压力激增. 产生原因: 缓存中有大量数据同时过期,导致大量请求无法得到处理 数据保存在缓存中,并设 ...

  8. Redis核心技术与实战-学习笔记(十五):消息队列(Redis的解决方案)

    一.消息队列 消息队列:分布式系统必备的一个基础软件,能支持组件通信消息的快速读写 Redis本身支持数据的快速访问,满足消息队列的读写性能需求 二.Redis适合做消息队列吗? 消息队列的消息存取需 ...

  9. Redis核心技术与实战-学习笔记(五)内存快照RDB

    一.为什么需要RDB AOF 方法优势:每次执行只需要记录操作命令,需要持久化的数据量不大.在进行写后日志只要不采用always(同步写回)的持久化策略就不会对性能造成太大影响. AOF方法劣势:AO ...

最新文章

  1. 【ACM】杭电OJ 5055(Bob and math problem)
  2. 微生物生态学中的挑战:建立对于群落功能与动态的预测性认识
  3. Android应用程序获得root权限
  4. UDP 组播---你需要了解这些
  5. 计算机视觉,不可能凉!
  6. Linux 编辑doc,Linux命令大全(文档编辑).doc
  7. mysql 设置 0、1 用什么数据类型_什么是MySQL数据库?看这一篇干货文章就够了!...
  8. jQuery中return false e preventDefault e stopPropagation 的区
  9. 重磅!激光SLAM算法及框架概述
  10. 《智能路由器开发指南》——2.6 参考资料
  11. 斯坦纳树算法概述及习题
  12. win7计算机重启遇到错误,安装Win7系统过程出现计算机意外地重新启动或遇到错误提示的解决方法...
  13. 华为手机序列号前三位_华为所有型号交换机查看序列号方法
  14. 计算机连接苹果手机不能找到照片目录,苹果手机上照片在电脑找不到了怎么办...
  15. 用记忆法记忆单词的M种方法 吴天胜
  16. MOOS-ivp之第一个MOOSApp:向MOOSDB发布数据
  17. 计算机网络知识面试常考
  18. 通过全局钩子发送自定义消息,实现进程通信
  19. 商店管理系统的设计与实现
  20. 【苹果代发家庭推imessage】软件安装利用X代码iMessage Extensitioniments Sage SDK 1

热门文章

  1. Python中的对日期时间的处理
  2. Midjourney如何给模特换衣服
  3. 利用OPENCV创作梵高艺术风格图片
  4. TensorFlow 像梵高一样作画
  5. #软工实践-个人项目-词频统计
  6. 【BZOJ-28921171】强袭作战大sz的游戏 权值线段树+单调队列+标记永久化+DP...
  7. Java 实现顺时针螺旋二维数组输出
  8. 全自动调节灯光强度的实现(仿真+程序+文档)
  9. 安卓手机怎么下载java游戏
  10. SQL中的笛卡尔你真的懂吗?