【Python】Python代码的单元测试

时间:2024-02-17 21:34:25

Python代码的单元测试

单元测试的概念

  • 定义:是指对软件中的最小可测试单元进行检查和验证。

  • 作用:可以确保程序模块是否否和我们规范的输出,保证该模块经过修改后仍然是满足我们的需求。

单元测试的策略

如果要创建单元测试,可以遵循如下基本技巧来确保涵盖所有的测试用例。

  • 逻辑检查:给定正确的、符合预期的输入,系统是否能够执行正确的计算并遵循通过代码正确的路径?给定的输入是否涵盖通过代码的所有路径?

  • 边界检查:对于给定的输入,系统如何响应? 系统如何响应典型输入、边缘用例或无效输入?假设您期望输入的整数介于 3 和 7 之间。当您使用 5(典型输入)、3(边缘用例)或 9(无效输入)时,系统会如何响应?

  • 错误处理:当输入中出现错误时,系统会如何响应? 是否提示用户输入其他内容? 软件是否会崩溃?

  • 面向对象的检查:如果通过运行代码更改任何持久对象的状态,则该对象是否正确更新?

单元测试类的编写

  1. 首先编写要进行单元测试的代码。

    def add(x, y):
        """加法函数"""
        return x + y
    
    def subtract(x, y):
        """减法函数"""
        return x - y
    
    def multiply(x, y):
        """乘法函数"""
        return x * y
    
    def divide(x, y):
        """除法函数"""
        if y != 0:
            return x / y
        else:
            return ValueError("除数不能为0")
  2. 编写测试工具代码。

    # 测试代码应使用test_xxx的名称进行规范
    # 这里使用断言assert进行模拟
    # assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常
    # 如下代码执行正常不报错,则说明函数执行符合预期
    from myfunctions import divide
    
    # 一个简单的单元测试示例
    def test_divide(x, y, result):
        r = divide(x, y)
        assert result == r
    
    
    def test_divide_error(x, y):
        try:
            divide(x, y)
        except ValueError:
            assert False
  3. 执行单元测试的工具代码。

    单元测试执行完成,无错误输出代码执行符合预期。

Python中的单元测试类

如上的代码能够满足Python中进行单元测试的需求,但如果换一套方法,我们的单元测试又需要手动编码,Python中提供了专业的单元测试工具类:unittest,以下是该单元测试类的使用方法介绍。

unittest 将我们常规用到的测试场景封装了以下断言方法,根据测试所需要的场景进行引用。

断言方法 方法解释
assertEqual(a, b) 检查a 和b 是否相等。
assertNotEqual(a, b) 检查a 和b 是否不相等。
assertTrue(x) 检查x 是否为True。
assertFalse(x) 检查x 是否为False。
assertIs(a, b) 检查a 和b 是否为同一对象(is)。
assertIsNot(a, b) 检查a 和b 是否不是同一对象。
assertIsNone(x) 检查x 是否为None。
assertIsNotNone(x) 检查x 是否不是None。
assertIn(a, b) 检查a 是否在b 中。
assertNotIn(a, b) 检查a 是否不在b 中。
assertIsInstance(a, b) 检查a 是否为b 类型的实例。
assertNotIsInstance(a, b) 检查a 是否不是b 类型的实例。
assertAlmostEqual(a, b) 检查a 和b 是否近似相等(适用于浮点数比较)。
assertNotAlmostEqual(a, b) 检查a 和b 是否不近似相等(适用于浮点数比较)。
assertRaises(Error, func, *args, **kwargs) 检查当调用function 时是否抛出了Error 异常。

unittest模块的基础使用

单元测试代码:

# unittest 是python自带的工具包,无需单独下载
import unittest
from myfunctions import *

class TestMyFunctions(unittest.TestCase):

    def test_add(self):
        # assertEqual,断言测试方法,`unittest` 将我们常规用到的测试场景封装成断言方法,根据测试所需要的场景进行引用。
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(-1, 2), 1)
        self.assertEqual(add(-1, -2), -3)

    def test_subtract(self):
        self.assertEqual(subtract(1, 2), -1)
        self.assertEqual(subtract(-1, 2), -3)
        self.assertEqual(subtract(-1, -2), 1)

    def test_multiply(self):
        self.assertEqual(multiply(1, 2), 2)
        self.assertEqual(multiply(-1, 2), -2)
        self.assertEqual(multiply(-1, -2), 2)

    def test_divide(self):
        self.assertEqual(divide(1, 2), 0.5)
        self.assertEqual(divide(-1, 2), -0.5)
        self.assertEqual(divide(-1, -2), 0.5)

unittest 代码并不能够直接运行,有以下执行方法:

  • 命令行:

    python -m unittest test_myfunctions.py

    注意:这里的启动如果写成 python.exe -m unittest .\test_myfunctions.py 会报如下错误。

  • main 函数:

    if __name__ == '__main__':
        unittest.main()

unittest模块的前置方法

在实际的测试中可能同时存在多个前置相同的测试,unittest模块提供了setUp()用于在测试开始前执行相关环境的设置,tearDown()setUp() 方法之后进行执行。

注:这里的 setUp 和 tearDown 是固定的函数名,不允许更改。

要测试的代码:

def divide(x, y):
    """除法函数"""
    print("divide called")
    if y != 0:
        return x / y
    else:
        raise ValueError("除数不能为0")

测试工具类:

import unittest
from myfunctions import *

class TestMyFunctions(unittest.TestCase):

    def setUp(self):
        self.test_value_a = 10
        self.test_value_b = 5
        self.test_value_c = 0
        print("setUp called")

    def tearDown(self):
        del self.test_value_a
        del self.test_value_b
        del self.test_value_c
        print("tearDown called")

    def test_divide(self):
        self.assertEqual(divide(self.test_value_a, self.test_value_b), 2)
        self.assertRaises(ValueError, divide, self.test_value_a, self.test_value_c)

if __name__ == '__main__':
    unittest.main()

代码执行测试:

可以看到,在执行函数测试之前,首先调用了 setUp() 前置方法,然后执行测试,测试结束后,调用 tearDown() 清理单元测试。

测试覆盖率

覆盖率是用来度量测试完整性的手段,是测试效果衡量的标准,是测试技术有效性的度量: 覆盖率 = (至少被执行一次的项目(item)数) / (项目的总数)

Python中提供了测试覆盖率的模块类:coverage,该第三方包需要手动安装。

这里使用如下方法直接进行代码的测试覆盖率分析

# 调用单元测试工具类
coverage run -m unittest discover
# 输出测试报告
coverage report

这里的代码测试覆盖率较低,对单元测试工具类进行优化,要测试的代码如下:

def add(x, y):
    """加法函数"""
    return x + y

def subtract(x, y):
    """减法函数"""
    return x - y

def multiply(x, y):
    """乘法函数"""
    return x * y

def divide(x, y):
    """除法函数"""
    print("divide called")
    if y != 0:
        return x / y
    else:
        raise ValueError("除数不能为0")

优化后的单元测试方法为:

import unittest
from myfunctions import *

class TestMyFunctions(unittest.TestCase):

    def setUp(self):
        self.test_value_a = 10
        self.test_value_b = 5
        self.test_value_c = 0
        print("setUp called")

    def tearDown(self):
        del self.test_value_a
        del self.test_value_b
        del self.test_value_c
        print("tearDown called")

    def test_add(self):
        self.assertEqual(add(self.test_value_a, self.test_value_b), 15)
        self.assertEqual(add(self.test_value_a, self.test_value_c), 10)

    def test_subtract(self):
        self.assertEqual(subtract(self.test_value_a, self.test_value_b), 5)
        self.assertEqual(subtract(self.test_value_a, self.test_value_c), 10)

    def test_multiply(self):
        self.assertEqual(multiply(self.test_value_a, self.test_value_b), 50)
        self.assertEqual(multiply(self.test_value_a, self.test_value_c), 0)

    def test_divide(self):
        self.assertEqual(divide(self.test_value_a, self.test_value_b), 2)
        self.assertRaises(ValueError, divide, self.test_value_a, self.test_value_c)

if __name__ == '__main__':
    unittest.main()

结果测试