目录python
import unittest # 将要被测试的排序函数 def sort(arr): l = len(arr) for i in range(0, l): for j in range(i + 1, l): if arr[i] >= arr[j]: tmp = arr[i] arr[i] = arr[j] arr[j] = tmp # 编写子类继承 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()
首先,咱们须要建立一个类TestSort, 继承类unittest.TestCase
; 而后,在这个类中定义相应的测试函数test_sort(), 进行测试。
注意,测试函数要以 test 开头,而测试函数的内部,一般使用assertEqual()、assertTrue()、assertFalse()和assertRaise()等
assert 语句对结果进行验证。算法
这个例子比较简单,若是碰到复杂的例子,须要用到单元测试的技巧。数据库
mock、side_effect和patch。这三者用法不同,但都是一个核心思想,即用虚假的实现,来替换掉被测试函数的一些依赖项,让咱们能把
更多的精力放在须要被测试的功能上。后端
mock 是单元测试中最核心最重要的一环。mock 是指经过一个虚假对象,来代替被测试函数或模块须要的对象。数组
举个例子,好比你要测一个后端API逻辑的功能性,但通常后端API都依赖于数据库、文件系统、网络等。这样,你就须要经过mock,来建立一些
虚假信息的数据库层、文件系统层、网络层对象,以即可以简单地对核心后端逻辑单元进行测试。网络
Python mock则主要使用 mock 或者 MagicMock 对象,示例以下:ide
import unittest from unittest.mock import MagicMock class A(unittest.TestCase): def m1(self): val = self.m2() self.m3(val) def m2(self): pass def m3(self, val): pass def test_m1(self): a = A() a.m2 = MagicMock(return_value="custom_val") 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(argv=['first-arg-is-ignored'], exit=False)
这段代码中,咱们定义了一个类的三个方法m1()、m2()、m3()。咱们须要对 m1() 进行单元测试,可是 m1() 取决于 m2() 和 m3()。若是m2()和m3()
的内部比较复杂,你就不能只是简单地调用m1()函数来进行测试,可能须要解决不少依赖项的问题。模块化
可是有了mock就好办了。咱们能够把m2()替换为一个返回具体数值的value, 把m3()替换为另外一个mock(空函数)。这样,测试m1()就很容易了,咱们能够测试
m1()调用m2(),而且用m2()的返回值调用m3()。函数
这样测试的m1()基本上毫无心义,看起来只是象征性的测了一下逻辑。真正的工业化代码,都是不少层模块相互逻辑调用的一个树形结构。单元测试须要测的是
某个节点的逻辑功能,mock 掉相关的依赖项是很是重要的。这也是为何会被叫作单元测试 unit test, 而不是其余的 integration test, end to end test。post
Mock Side Effect 就是mock的函数,属性是能够根据不一样的输入,返回不一样的数值,而不仅是一个return_value。
好比下面这个例子,测试的是输入参数是否为负数,输入小于0则输出为1,不然输出为2。这就是 Mock Side Effect 的用法。
from unittest.mock import MagicMock def side_effect(arg): if arg < 0: return 1 else: return 2 mock = MagicMock() mock.side_effect = side_effect mock(-1) mock(1)
至于patch,给开发者提供了很是便利的函数 mock 方法。它能够应用 Python 的 decoration 模式或是 context manager 概念,快速天然地 mock
所须要的函数。它的用法也不难,咱们来看代码:
from unittest.mock import patch @patch('sort') def test_sort(self, mock_soft): ... ...
在这个 test 里面,mock_sort 替代 sort 函数自己的存在,因此咱们能够像开始提到的 mock object 同样,设置 return_value 和 side_effect。
另外一种 patch 的常见用法,是mock类的成员函数,这个技巧咱们在工做中也常常会用到,好比说一个类的构造函数很是复杂,而测试其中一个成员函数并不
依赖全部初始化的 object。它的用法以下:
with patch.object(A, '__init__', lambda x: None): ...
在 with 语句里面,咱们经过 patch,将 A 类的构造函数 mock 为一个 do nothing 的函数,这样就能够很方便地避免一些复杂的初始化(initialization)。
综上,单元测试的核心仍是 mock, mock 掉依赖项,测试相应的逻辑或算法的准确性。
咱们能够用 Python 的 coverage tool 来衡量 Test Coverage, 而且显示每一个模块为被 coverage 的语句
好比,咱们写了一个下面这个函数,对一个数组进行处理,并返回新的数组:
def work(arr): # pre process ... ... # sort l = len(arr) for i in range(0, l): for j in range(i + 1, j): if arr[i] >= arr[j]: tmp = arr[i] arr[i] = arr[j] arr[j] = tmp # post process ... ... return arr
这段代码的大概意思是,现有个预处理,再排序,最后在处理一下而后返回,这时候咱们就能够用到模块化:
def preprocess(arr): ... ... return arr def sort(arr): ... ... return arr def postprocess(arr): ... return arr def work(self): arr = preprocess(arr) arr = sort(arr) arr = postprocess(arr) return arr
接着再进行相应的测试,测试三个子函数的功能正确性;而后经过 mock 子函数,调用 work() 函数,来验证三个子函数被 call 过。
from unittest.mock import patch def test_preprocess(self): ... def test_sort(self): ... def test_postprocess(self): ... @patch('%s.preprocess') @patch('%s.sort') @patch('%s.postprocess') def test_work(self,mock_post_process, mock_sort, mock_preprocess): work() self.assertTrue(mock_post_process.called) self.assertTrue(mock_sort.called) self.assertTrue(mock_preprocess.called)