单元测试 笔记

简单事例

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 是单元测试中最核心最重要的一环。mock 是指通过一个虚假对象,来代替被测试函数或模块需要的对象。

举个例子,比如你要测一个后端API逻辑的功能性,但一般后端API都依赖于数据库、文件系统、网络等。这样,你就需要通过mock,来创建一些
虚假信息的数据库层、文件系统层、网络层对象,以便可以简单地对核心后端逻辑单元进行测试。

Python mock则主要使用 mock 或者 MagicMock 对象,示例如下:

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。

Mock Side Effect

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

至于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 掉依赖项,测试相应的逻辑或算法的准确性。

高质量单元测试的关键

Test Coverage

我们可以用 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)
原文地址:https://www.cnblogs.com/wuyongqiang/p/11208590.html