【Python】Python代码的单元测试

Python代码的单元测试

单元测试的概念

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

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

单元测试的策略

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

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

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

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

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

单元测试类的编写

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

    md-end-block 复制代码
    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. 编写测试工具代码。

    md-end-block 复制代码
    # 测试代码应使用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模块的基础使用

单元测试代码:

md-end-block 复制代码
# 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 代码并不能够直接运行,有以下执行方法:

  • 命令行:

    bash 复制代码
    python -m unittest test_myfunctions.py

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

  • main 函数:

    md-end-block 复制代码
    if __name__ == '__main__':
        unittest.main()

unittest模块的前置方法

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

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

要测试的代码:

md-end-block 复制代码
def divide(x, y):
    """除法函数"""
    print("divide called")
    if y != 0:
        return x / y
    else:
        raise ValueError("除数不能为0")

测试工具类:

md-end-block 复制代码
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,该第三方包需要手动安装。

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

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

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

md-end-block 复制代码
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")

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

md-end-block 复制代码
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()

结果测试

相关推荐
云泽野3 小时前
【Java|集合类】list遍历的6种方式
java·python·list
IMPYLH4 小时前
Python 的内置函数 reversed
笔记·python
小赖同学啊6 小时前
物联网数据安全区块链服务
开发语言·python·区块链
码荼7 小时前
学习开发之hashmap
java·python·学习·哈希算法·个人开发·小白学开发·不花钱不花时间crud
小陈phd8 小时前
李宏毅机器学习笔记——梯度下降法
人工智能·python·机器学习
kk爱闹8 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
Blossom.1188 小时前
机器学习在智能建筑中的应用:能源管理与环境优化
人工智能·python·深度学习·神经网络·机器学习·机器人·sklearn
亚力山大抵8 小时前
实验六-使用PyMySQL数据存储的Flask登录系统-实验七-集成Flask-SocketIO的实时通信系统
后端·python·flask
showyoui8 小时前
Python 闭包(Closure)实战总结
开发语言·python
amazinging9 小时前
北京-4年功能测试2年空窗-报培训班学测开-第四十一天
python·学习·appium