软件测试之单元测试unittest库使用、参数化、unittestteport

devtools/2025/3/17 10:50:48/

文章目录

  • 前言
  • 什么是单元测试
  • 什么是单元测试框架
  • Unittest 测试框架
    • Unittest核心要素
      • TestCase
      • Fixture
      • TestSuite
      • TextTestRunner
      • report
    • Unittest 断言
  • Unittest 框架使用
    • 编写TestCase(测试用例)
    • 在测试用例中添加Fixture(测试夹具)
    • 将测试用例添加至TestSuite(测试套件)
      • TestLoader介绍
    • 使用TextTestRunner执行测试用例套件
    • 输出测试报告
    • 跳过(补充)
  • 参数化
    • 定义
    • parameterized模块的实现步骤
    • ddt模块的实现步骤
  • 参考目录


前言

阅读本文前请注意最后编辑时间,文章内容可能与目前最新的技术发展情况相去甚远。欢迎各位评论与私信,指出错误或是进行交流等。


什么是单元测试

单元测试(unit testing)是指对软件中的最小可测试单元进行检查和验证。 单元测试中单元的含义,单元就是人为规定的最小的被测功能模块,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。

什么是单元测试框架

理论上来说,不使用单元测试框架也能进行单元测试。但如果用于单元测试的代码(即测试用例)增多,在没有测试框架的情况下会变得拥挤、不可管理,这个时候引入测试框架就变得尤为重要。

单元测试框架提供了一种统一的编程模型,可以将测试定义为一些简单的类,这些类中的方法可以调用希望测试的应用程序代码。利用单元测试框架,可以很轻松地插入、设置和分解有关测试的功能,可以直观方便地管理测试用例。

主流的单元测试框架,如Java的Junit、python的Unittest、Pytest

单元测试框架作用:

  • 提供用例组织与执行
  • 提供丰富的断言方法
  • 提供丰富的日志与测试结果

Unittest 测试框架

Unittest是Python自带的单元测试框架,不仅适用于单元测试,还可用于Web、Appium、接口自动化测试用例的开发与执行。该测试框架可组织执行测试用例,并且提供丰富的断言方法,判断测试用例是否通过,并最终生成测试结果。

Unittest官方文档:unittest — Unit testing framework — Python 3.10.4 documentation

Unittest核心要素

TestCase

即测试用例,Unittest提供testCase类来编写测试用例,一个TestCase的实例就是一个测试用例。一条测试用例就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown),通过运行一条测试用例,可以对某一个问题进行验证。
在这里插入图片描述

Fixture

即测试固件,用于测试用例环境的搭建和销毁。在测试步骤执行前需要为该测试用例准备环境(SetUp),如启动app或打开浏览器,测试步骤执行后需要恢复环境 (TearDown),如关闭app或浏览器,这时候就需要用到Fixture,使代码更简洁。

TestSuite

即测试套件,把需要执行的测试用例集合在一起就是TestSuite。使用TestLoader来加载TestCase到TestSuite中。
在这里插入图片描述

TextTestRunner

即测试执行器,用于执行测试套件。该模块中提供run方法执行TestSuite中的测试用例,并返回测试用例的执行结果,如运行的用例总数、用例通过数、用例失败数。
在这里插入图片描述

report

即测试报告。unittest框架没有自带的用于生成测试报告的模块或接口,需要使用第三方的扩展模块。

Unittest 断言

在这里插入图片描述
补充:
如果断言成功则该条测试用例通过,断言失败则该条测试用例执行失败,且会抛出AssertionError错误。
以上提供的断言方法中,都有一个msg参数,默认为None。如果msg参数有对应的值,则断言失败后该msg的值会作为失败信息返回,如 assertEqual(a, b, msg=“a与b不相等!”) 。

Unittest 框架使用

测试对象:构造一个类Math,其中包含整数的加、减法运算。

calculator.py

class Math():def __init__(self, a, b):self.a = int(a)self.b = int(b)def sum(self):'''和'''return self.a + self.bdef sub(self):'''差'''return self.a - self.b

接下来对这个类Math测试,使用unittest框架编写测试用例。

编写TestCase(测试用例)

在Unittest框架下创建测试用例,步骤如下:

  • 1),导入unittest模块。
  • 2),创建测试类。测试类的命名不做要求,但需要继承unittest.TestCase类。
  • 3)添加setUp()、tearDown()函数,即测试固件。(非必须)
  • 4),定义测试方法,即测试用例。测试方法名称必须以test开头,否则测试时该方法将不会被执行。测试方法里需要添加断言。
  • 5),调试执行测试用例。执行当前模块的测试用例时,调用unittest.main()方法,该方法会搜索该模块下所有以test开头的测试用例方法,并执行。

编写测试用例 代码如下:
test_sum.py

import unittest
from calculator import Mathclass SumTest(unittest.TestCase):'''测试Math类中的sum函数'''def setUp(self):print("开始执行测试用例{}...".format(self))def test_sum01(self):m = Math(3, 4)self.assertEqual(m.sum(), 7)def test_sum02(self):m = Math(2, 8)self.assertEqual(m.sum(), 11)def tearDown(self):print("测试用例{}执行结束...".format(self))if __name__ == '__main__':unittest.main()

运行结果:

.F
======================================================================
FAIL: test_sum02 (__main__.SumTest)
----------------------------------------------------------------------
Traceback (most recent call last):File "C:/Users/xiaoqq/Desktop/test_project/demo/testSum.py", line 15, in test_sum02self.assertEqual(m.sum(), 11)
AssertionError: 10 != 11----------------------------------------------------------------------
Ran 2 tests in 0.001sFAILED (failures=1)
开始执行测试用例test_sum01 (__main__.SumTest)...
测试用例test_sum01 (__main__.SumTest)执行结束...
开始执行测试用例test_sum02 (__main__.SumTest)...
测试用例test_sum02 (__main__.SumTest)执行结束...Process finished with exit code 0

结果显示:

  • test_sum01通过,test_sum02失败。点"."表示通过,"F"表示失败。
  • 断言失败会返回一个AssertionError。

在测试用例中添加Fixture(测试夹具)

在Unittest框架下的测试用例中,使用Fixture有三种方法:

setUp()、tearDown(),作用于测试方法。即测试类下的每个测试方法执行前先运行setUp(),每个测试方法执行完毕后都要执行tearDown()方法,如testSum.py示例。
setUpClass()、tearDownClass(),作用于测试类。即只在整个测试类执行开始时运行setUpClass(),测试类下所有测试方法执行完后运行tearDownClass()。且需要使用装饰器@classmethod
setUpModule()、tearDownModule(),作用于测试模块(可以理解为整个TestCase.py文件)。即只在整个测试模块执行开始时运行setUpModule(),测试类下所有测试模块执行完后运行tearDownModule()。

以setUpClass()、tearDownClass() 举例
test_sum.py修改如下,运行

class SumTest(unittest.TestCase):'''测试Math类中的sum函数'''@classmethoddef setUpClass(cls):print("开始执行测试用例{}...".format(cls))def test_sum01(self):m = Math(3, 4)self.assertEqual(m.sum(), 7)def test_sum02(self):m = Math(2, 8)self.assertEqual(m.sum(), 11)@classmethoddef tearDownClass(cls):print("测试用例{}执行结束...".format(cls))if __name__ == '__main__':unittest.main()

运行结果:

开始执行测试用例<class '__main__.SumTest'>...
测试用例<class '__main__.SumTest'>执行结束...
.F

结果显示,setUpClass()、tearDownClass() 都只运行了一次。注意,这里需要使用装饰器@classmethod

注意

  • 在测试用例中,setUp() 或 setUpClass() 做初始化的工作,不是必须有这个函数。同样tearDown() 和 tearDownClass() 只做清理的工作,在测试类中不是必须要有。
  • 需要测试的Math类,代码比较简单,没有真正需要用到测试夹具的地方,因此这只是个用法演示。
  • 实际自动化过程中,如Web端UI自动化,一般会将创建浏览器实例放在setUp() ,用例执行完后需要关闭浏览器,将关闭浏览器操作放在tearDown()方法里。示例如下:
import unittest
from selenium import webdriverclass BaiduTest(unittest.TestCase):def setUp(self):'''打开浏览器,进入百度页面'''self.driver = webdriver.Chrome()self.driver.maximize_window()self.driver.get('https://www.baidu.com')def test_01(self):print("操作步骤")def tearDown(self):'''关闭浏览器'''self.driver.quit()

将测试用例添加至TestSuite(测试套件)

在testSum.py模块中,使用了unittest.main()方法执行当前模块里的测试用例。除此之外,Unittest还可以通过测试套件构造测试用例集,再执行测试用例。构造TestSuite常用的方法如下:

4.1,方法一:加载测试用例

  • 1),先通过unittest.TestSuite() 创建测试套件实例对象。

  • 2),再通过addTest() 往测试套件里添加单个测试用例,或通过addTests([…]) 添加多个测试用例(列表中为用例方法名)。

  • 3),执行测试套件里的测试用例

run.py示例:

import unittest
# 导入测试用例模块
from testcase.test_sum import TestDemo# 第一步:创建TestSuite实例
suite = unittest.TestSuite()# 第二步:将测试用例添加至TestSuite
# 方式1,添加单条测试用例
suite.addTest(TestDemo('test_sum01'))   # addTest()里参数格式为:测试类('测试方法')
suite.addTest(TestDemo('test_sum02'))# 方式2,添加多条测试用例
suite.addTests([TestDemo('test_sum01'), TestDemo('test_sum02')])

TestLoader介绍

下文方法二和方法三都是TestLoader的使用
在这里插入图片描述

方法二:加载测试用例类

1),先通过unittest.TestSuite() 创建测试套件实例对象。

2),再通过unittest.TestLoader()创建加载对象,加载测试用例类

run.py示例:

import unittest
# 导入测试用例模块
from testcase.test_sum import TestDemo# 创建TestSuite实例
suite = unittest.TestSuite()
# 创建一个加载对象
loader = unittest.TestLoader()
suite.addTest(loader.loadTestsFromTestCase(TestDemo))

方法三:加载指定路径里的测试用例

1),通过unittest.defaultTestLoader.discover()将指定路径的测试用例加载至测试用例集。注意:这里不需要创建unittest.TestSuite对象
2),如下代码所示,test_dir为指定路径。pattern=test_*.py 表示加载以test_开头的模块中的测试用例,指定运行某模块时pattern参数指定全名即可,如:pattern=‘test_sum.py’。路径跟pattern参数都可以自定义。

run.py示例

import unittesttest_dir = './testcase'
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')

使用TextTestRunner执行测试用例套件

unittest框架执行测试用例之前,需先创建TextTestRunner实例,再调用该实例的run()方法。

# 创建TextTestRunner实例
runner = unittest.TextTestRunner()
# 使用run()方法运行测试套件(即运行测试套件中的所有用例)
runner.run(suite)

run.py修改成如下示例:

import unittesttest_dir = './testcase'
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')runner = unittest.TextTestRunner()
runner.run(suite)

运行run.py,结果如下:

.F
======================================================================
FAIL: test_sum02 (test_sum.TestDemo)
----------------------------------------------------------------------
Traceback (most recent call last):File "C:\Users\xiaoqq\Desktop\test_project\demo\test_sum.py", line 15, in test_sum02self.assertEqual(m.sum(), 11)
AssertionError: 10 != 11----------------------------------------------------------------------
Ran 2 tests in 0.000sFAILED (failures=1)
开始执行测试用例test_sum01 (test_sum.TestDemo)...
测试用例test_sum01 (test_sum.TestDemo)执行结束...
开始执行测试用例test_sum02 (test_sum.TestDemo)...
测试用例test_sum02 (test_sum.TestDemo)执行结束...Process finished with exit code 0

输出测试报告

unittest框架执行测试用例完成后会在控制台输出如上的结果,但实际测试过程中,我们需要输出测试报告,这个时候我们需要使用第三方模块。例如 HTMLTestRunner,如果对该模块使用有需求可查看该博客软件测试——Unittest单元测试框架详解,或者查看该模块的官方文档。

本文介绍的是另外的一个第三方模块,unittestreport

UnitTestReport 是一个基于 Python 的开源库,专为改进 unittest 框架的测试报告体验而生。它不仅解决了标准 unittest 报告样式单一且不够直观的问题,还添加了许多实用特性,如生成丰富的 HTML 测试报告、支持数据驱动测试、测试用例的失败重试、多线程并发执行、以及自动化测试结果的通知功能,如发送至邮箱或企业协作平台如钉钉和企业微信。

该项目由木森发起并维护,旨在通过提供详细且易于理解的测试报告,提升软件开发过程中的测试质量和效率。无论是小型项目还是大型企业级应用的测试流程,UnitTestReport都能助力团队更有效地管理和分析测试结果。

github地址:UnitTestReport
使用文档:UnitTestReport使用文档

目前UnitTestReport实现了以下功能:

  • HTML测试报告生成
  • unittest数据驱动
  • 测试用例失败重运行
  • 多线程并发执行用例
  • 发送测试结果及报告到邮箱
  • 测试结果推送到钉钉
  • 测试结构推送到企业微信
  • 使用pytest生成unittestreport的html报告(最新)

报告样式展示:
在这里插入图片描述
至于具体用法大家参考官方使用文档即可,文档中都已经给出了详细的用法、例子、解释。

跳过(补充)

在这里插入图片描述
代码

import unittestversion = 30class TestDemo(unittest.TestCase):# 使用skip装饰器表示跳过@unittest.skip('没什么原因')def test_1(self):print('测试方法1')@unittest.skipIf(version >= 30, '版本大于等于30,不用测试')def test_2(self):print('测试方法2')def test_3(self):print('测试方法3')

运行结果
在这里插入图片描述
可以发现,跳过了测试方法1,并根据判断跳过了不满足条件的测试方法2.

参数化

定义

在这里插入图片描述

parameterized模块的实现步骤

首先使用 pip install parameterized 安装参数化插件。
parameterized 包的使用如下图所示
1.

ddt模块的实现步骤

我们设计测试用例时,会出现测试步骤一样,只是其中的测试数据有变化的情况,比如测试登录时的账号密码。这个时候,如果我们依然使用一条case一个方法的话,会出现大量的代码冗余,而且效率也会大大降低。此时,ddt模块就能帮助我们解决这个问题。

ddt(data-driven test),顾名思义,数据驱动测试。这个模块是第三方库,需要我们自己下载。或者直接在命令行输入pip install ddt。

使用方法:

  1. 使用数据驱动,要在class前加上修饰器 @ddt
import unittest
from ddt import ddt, data@ddt  
class TestDemo(unittest.TestCase):# 单一参数@data('17611110000', '17611112222')def test_1(self, phone):print('测试一电话号码:', phone)if __name__ == '__main__':unittest.main()
else:pass
  1. 在实际中不一定是单一参数进行传参,将会使用多个参数进行传参:

注意事项:
1)、多个数据传参的时候@data里面是要用列表形式
2)、会用到 @unpack 装饰器 进行拆包,把对应的内容传入对应的参数;

import unittest
from ddt import ddt, data, unpack@ddt
class TestDemo(unittest.TestCase):# 多参数数据驱动@data(['admin', '123456'])# unpack 是进行拆包,不然会把列表里面的数据全部传到username这个一个参数,我们要实现列表中的两个数据分别传入对应的变量中@unpackdef test_2(self, username, password):print('测试二:', username, password)if __name__ == '__main__':unittest.main()
else:pass

但是以上步骤都是数据在代码当中的,假如要测试n个手机号这样的数据,全部写在 @data 装饰器里面就很麻烦,这就引出了数据驱动里面的代码和数据的分离。
将数据放入一个文本文件中,从文件读取数据, 如JSON、 excel、 xml、 txt等格式文件

  1. json文件数据
[{"username": "admin","password": "123456"},{"username": "normal","password": "45678"}
]

在测试代码中读取json文件

import json
import unittest
from ddt import ddt, data, unpack# 用json多个参数读取
def reads_phone():with open('user.json', encoding='utf-8') as f:result = json.load(f)  # 列表return result@ddt
class TestDemo(unittest.TestCase):# 多参数数据驱动@data(*reads_phone())# unpack 是进行拆包,不然会把列表里面的数据全部传到username这个一个参数,我们要实现列表中的两个数据分别传入对应的变量中@unpackdef test_2(self, username, password):print('测试二:', username, password)if __name__ == '__main__':unittest.main()
else:pass

注意事项:
1、with open里面默认是 ”r“
2、@data 里面的 * 含义是实现每个json对象单个传入方法执行,不然会吧json文件里面所用数据全部传入
> * 是元祖;
> ** 是字典;
3、参数不能传错,要对应

执行结果:
在这里插入图片描述

  1. txt文件驱动, 一行表示一组数据:
admin,123456
normal,456789
 
import unittest
def read():lis = []with open('readtext.txt', 'r', encoding='utf-8') as f:for line in f.readlines():# lis.append(line) #  ['admin,123456\n', 'normal,456789\n']# lis.append(line.strip('\n'))  ['admin,123456', 'normal,456789'] 两个字符串lis.append(line.strip('\n').split(','))  # [['admin', '123456'], ['normal', '456789']]return lisclass TestDome(unittest.TestCase):def test_01(self):li = read()print(li)if __name__ == '__main__':unittest.main()

剩余的包括CSV、yaml等文件类型操作 可以参考这篇博客自动化测试之unittest框架详解


参考目录

https://blog.csdn.net/qaqqqqqaq_/article/details/135389040
https://blog.csdn.net/Asaasa1/article/details/109402334
https://blog.csdn.net/Bala_lala/article/details/123362849
https://blog.csdn.net/HUA1211/article/details/136660873
https://www.bilibili.com/video/BV11g411V7Kf
https://www.bilibili.com/video/BV12b4y1J7H6
https://www.bilibili.com/video/BV19M4y147i6
https://www.bilibili.com/video/BV1TP411u7Ti


http://www.ppmy.cn/devtools/167797.html

相关文章

浅谈AVL树插入的平衡调节

文章目录 1. AVL树介绍1.1 什么是AVL树1.2 AVL树结构代码展示 2. AVL树插入的平衡调节2.1 插入后节点的平衡因子为02.2 插入后节点的平衡因子为1或-12.2 插入后节点的平衡因子为2或-22.2.1 左单旋2.2.2 右单旋2.2.3 左右双旋2.2.4 右左双旋 3. AVL树插入的具体代码3.1 插入接口…

【华为OD-E卷 -121 消消乐游戏 100分(python、java、c++、js、c)】

【华为OD-E卷 - 消消乐游戏 100分(python、java、c++、js、c)】 题目 游戏规则:输入一个只包含英文字母的字符串,字符串中的两个字母如果相邻且相同,就可以消除。 在字符串上反复执行消除的动作,直到无法继续消除为止,此时游戏结束。 输出最终得到的字符串长度 输入描…

TCP和UDP的区别

一&#xff1a;连接性 TCP是面向连接的协议&#xff0c;传输时序先三次握手建立连接&#xff0c;确保双方都准备好通信。 UDP是无连接的协议&#xff0c;发送数据前不需要建立连接&#xff0c;直接把数据发给对方&#xff0c;不管对面收不收到。 二&#xff1a;可靠性 TCP提供可…

Cursor的使用感受,帮你使用好自动化编程工具,整理笔记

使用感受 说实话&#xff0c;我觉得cursor还是好用的&#xff0c;可能我刚开始使用&#xff0c;没有使用的非常的熟练&#xff0c;运用也没有非常的透彻&#xff0c;总体体验还是不错的&#xff0c;在使用它时&#xff0c;我优先考虑&#xff0c;前端页面功能复用的时候&#…

coze ai assistant Task 3

这是我第一次尝试Coze工作流&#xff0c;以前在工作中一直使用RPA&#xff0c;偶然听说了AI工作流就想来尝试一下&#xff0c;同时也想探究RPA与Coze的不同之处&#xff0c;是替代还是可以融合&#xff0c;目前还在尝试中&#xff0c;等整体结束后会写一篇感想。 Coze工作流支持…

AI战略家:AI政务应用思考——AI与区块链融合对政府权力结构的重构:从“技术赋能”到“制度革命”

一、AI区块链的治理模式&#xff1a;技术特性与权力挑战 去中心化与透明性&#xff1a;对权威的“解构” 独立决策逻辑&#xff1a;当AI结合区块链技术&#xff0c;其决策过程将基于算法预设规则与实时数据分析&#xff0c;形成不可篡改的决策链。例如&#xff0c;美国白宫提出…

【AI大模型智能应用】Deepseek生成测试用例

在软件开发过程中&#xff0c;测试用例的设计和编写是确保软件质量的关键。 然而&#xff0c;软件系统的复杂性不断增加&#xff0c;手动编写测试用例的工作量变得异常庞大&#xff0c;且容易出错。 DeepSeek基于人工智能和机器学习&#xff0c;它能够依据软件的需求和设计文…

CentOS系统中使用sendmail

在CentOS系统中&#xff0c;如果你想要使用sendmail来发送电子邮件&#xff0c;你可以通过以下步骤来配置和测试它。sendmail是Linux系统上常用的邮件传输代理&#xff08;MTA&#xff09;&#xff0c;它可以用来发送邮件。 步骤1&#xff1a;安装sendmail 首先&#xff0c;你…