单元测试与unittest框架

🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,薪资嘎嘎涨

单元测试的定义

1. 什么是单元测试?

单元测试是指,对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常是指函数或者类,一般是开发来做的,按照测试阶段来分,就是单元测试、集成测试、系统测试以及验收测试。

2.为什么要做单元测试?

📕 单元测试之后,才是集成测试,单个单个的功能模块测试通过之后,才能把单个功能模块集成起来做集成测试,为了从底层发现bug,单元测试时可以减少合成后出现的问题。
📒 越早发现bug越好,这样可以早点发现问题,不然问题累计到后面,很可能会因为一个做错了而导致整个模块甚至更大范围的推倒重来,对于时间和经费来说,是非常浪费的!
📘 对于测试来说,单元测试就是为了执行用例,输入测试数据--》输出测试结果

unittest框架及原理

做过自动化测试的同学应该都知道python中的unittest框架,它是python自带的一套测试框架,学习起来也相对较容易,unittest框架最核心的四个概念:

​ 🍊 test case:就是我们的测试用例,unittest中提供了一个基本类TestCase,可以用来创建新的测试用例,一个TestCase的实例就是一个测试用例;unittest中测试用例方法都是以test开头的,且执行顺序会按照方法名的ASCII值排序。

​ 🍅 test fixure:测试夹具,用于测试用例环境的搭建和销毁。即用例测试前准备环境的搭建(SetUp前置条件),测试后环境的还原(TearDown后置条件),比如测试前需要登录获取token等就是测试用例需要的环境,运行完后执行下一个用例前需要还原环境,以免影响下一条用例的测试结果。

​ 🍋 test suite:测试套件,用来把需要一起执行的测试用例集中放到一块执行,相当于一个篮子。我们可以使用TestLoader来加载测试用例到测试套件中。

​ 🍇 test runner:用来执行测试用例的,并返回测试用例的执行结果。它还可以用图形或者文本接口,把返回的测试结果更形象的展现出来,如:HTMLTestRunner。

unittest的断言

在python基础中,我们有讲过一个assert断言,使用方法比较简单,即assert 表达式, 提示信息,而unittest框架中也提供了一个自带的断言方式,主要有以下几种:

如果断言失败即不通过就会抛出一个AssertionError断言错误,成功则标识为通过,以上几种方式都有一个共同点,就是都有一个msg参数(表中只列了一个,其实都有),默认是None,即msg = None,如果指定msg参数的值,则将该信息作为失败的错误信息返回。

TestCase测试用例

编写测试用例前,我们需要建一个测试类继承unittest里面的TestCase类,继承这个类之后我们才是真正的使用unittest框架去写测试用例,编写测试用例的步骤如下:

  • 导入unittest模块
  • 创建一个测试类,并继承unittest.TestCase()
  • 定义测试方法,方法名必须以test_开头
  • 调用unittest.main()方法来运行测试用例,unittest.main()方法会搜索该模块下所有以test开头的测试用例方法,并自动执行

下面以注册功能为例,这个register.py就是注册功能的代码,没有前端界面,功能比较简单,只是方便用于演示,直接导入就可以使用。

# register.py
users = [{'username': 'test', 'password': '123456'}]def register(username, password1, password2):if not all([username, password1, password2]):return {"code": 0, "msg": "所有参数不能为空"}# 注册功能for user in users:if username == user['username']:return {"code": 0, "msg": "该用户名已存在!"}else:if password1 != password2:return {"code": 0, "msg": "两次密码输入不一致!"}else:if 6 <= len(username) >= 6 and 6 <= len(password1) <= 18:users.append({'username': username, 'password': password2})return {"code": 1, "msg": "注册成功"}else:return {"code": 0, "msg": "用户名和密码必须在6-18位之间"}

下面是编写测试用例例子:

# test_register.py
import unittest
from register import register   # 导入被测试的代码class TestRegister(unittest.TestCase):"""注册接口测试用例类"""def test_register_success(self):"""注册成功"""data = ("mikitest", "miki123", "miki123")   # 测试数据expected = {"code": 1, "msg": "注册成功"}   # 预期结果result = register(*data)    # 把测试数据传到被测的代码,接收实际结果self.assertEqual(expected, result)  # 断言,预期和实际是否一致,一致即用例通过def test_username_isnull(self):"""注册失败-用户名为空"""data = ("", "miki123", "miki123")expected = {"code": 0, "msg": "所有参数不能为空"}result = register(*data)self.assertEqual(expected, result)def test_username_lt6(self):"""注册失败-用户名大于18位"""data = ("mikitestmikitestmikitest", "miki123", "miki123")expected = {"code": 0, "msg": "用户名和密码必须在6-18位之间!"}result = register(*data)self.assertEqual(expected, result)    # 这条用例应该是不通过的,注册代码bugdef test_pwd1_not_pwd2(self):"""注册失败-两次密码不一致"""data = ("miki123", "test123", "test321")expected = {"code": 0, "msg": "两次密码输入不一致!"}result = register(*data)self.assertEqual(expected, result)# 如果直接运行这个文件,需要使用unittest中的main函数来执行测试用例
if __name__ == '__main__':unittest.main()

测试用例运行结果如下,一共4条用例,其中通过3条,不通过1条,不通过的是本身注册代码的bug。

Testing started at 21:58 ...
C:\software\python\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --target test_register.TestRegister
Launching unittests with arguments python -m unittest test_register.TestRegister in D:\learn\python_test{'code': 1, 'msg': '注册成功!'} != {'code': 0, 'msg': '用户名和密码必须在6-18位之间!'}Expected :{'code': 0, 'msg': '用户名和密码必须在6-18位之间!'}
Actual   :{'code': 1, 'msg': '注册成功!'}
<Click to see difference>Traceback (most recent call last):File "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\teamcity\diff_tools.py", line 32, in _patched_equalsold(self, first, second, msg)File "C:\software\python\lib\unittest\case.py", line 839, in assertEqualassertion_func(first, second, msg=msg)File "C:\software\python\lib\unittest\case.py", line 1138, in assertDictEqualself.fail(self._formatMessage(msg, standardMsg))File "C:\software\python\lib\unittest\case.py", line 680, in failraise self.failureException(msg)
AssertionError: {'code': 0, 'msg': '用户名和密码必须在6-18位之间!'} != {'code': 1, 'msg': '注册成功!'}
- {'code': 0, 'msg': '用户名和密码必须在6-18位之间!'}
+ {'code': 1, 'msg': '注册成功!'}During handling of the above exception, another exception occurred:Traceback (most recent call last):File "C:\software\python\lib\unittest\case.py", line 59, in testPartExecutoryieldFile "C:\software\python\lib\unittest\case.py", line 615, in runtestMethod()File "D:\learn\python24\python_base\day13_task\test_register.py", line 36, in test_username_lt6self.assertEqual(expected, result)Ran 4 tests in 0.007sFAILED (failures=1)Process finished with exit code 1Assertion failed

TestFixure测试夹具

unittest的测试夹具有两种使用方式,一种是以测试方法为维度的setUp()和tearDown(),一种是以测试类为维度的setUpClass()和tearDownClass()。以注册功能为例,但这个注册代码比较简单,没有真正需要用到测试夹具的地方,因此这只是个用法演示。

# test_register.py
import unittest
from register import register   # 导入被测试的代码class TestRegister(unittest.TestCase):"""注册接口测试用例类"""def setUp(self):# 每条用例执行之前都会执行print("用例{}开始执行--".format(self))def tearDown(self):# 每条用例执行之后都会执行print("用例{}执行结束--".format(self))@classmethod    # 指明这是个类方法以类为维度去执行的def setUpClass(cls):# 整个测试用例类中的用例执行之前,会先执行此方法print("-----setup---class-----")@classmethoddef tearDownClass(cls):# 整个测试用例类中的用例执行完之后,会执行此方法print("-----teardown---class-----")def test_register_success(self):"""注册成功"""data = ("mikitest", "miki123", "miki123")   # 测试数据expected = {"code": 1, "msg": "注册成功!"}   # 预期结果result = register(*data)    # 把测试数据传到被测的代码,接收实际结果self.assertEqual(expected, result)  # 断言,预期和实际是否一致,一致即用例通过def test_username_isnull(self):"""注册失败-用户名为空"""data = ("", "miki123", "miki123")expected = {"code": 0, "msg": "所有参数不能为空!"}result = register(*data)self.assertEqual(expected, result)# 如果直接运行这个文件,需要使用unittest中的main函数来执行测试用例
if __name__ == '__main__':unittest.main()

运行结果:

Testing started at 22:19 ...
C:\software\python\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --path D:/learn/python/test_register.py
Launching unittests with arguments python -m unittest D:/learn/python/test_register.py in D:\learn\python
-----setup---class-----用例test_pwd1_not_pwd2 (test_register.RegisterTestCase)开始执行--
用例test_pwd1_not_pwd2 (test_register.RegisterTestCase)执行结束--
用例test_register_success (test_register.RegisterTestCase)开始执行--
用例test_register_success (test_register.RegisterTestCase)执行结束--
用例test_username_isnull (test_register.RegisterTestCase)开始执行--
用例test_username_isnull (test_register.RegisterTestCase)执行结束--
用例test_username_lt6 (test_register.RegisterTestCase)开始执行--
用例test_username_lt6 (test_register.RegisterTestCase)执行结束--
-----teardown---class-----Ran 4 tests in 0.003sOKProcess finished with exit code 0

TestSuite测试套件

unittest.TestSuite()类来表示一个测试用例集,把需要执行的用例类或模块存到一起,常用的方法如下:

🍊 unittest.TestSuite()

  • addTest():添加单个测试用例方法
  • addTest([..]):添加多个测试用例方法,方法名存在一个列表

🍊 unittest.TestLoader()

  • loadTestsFromTestCase(测试类名):添加一个测试类
  • loadTestsFromModule(模块名):添加一个模块
  • discover(测试用例的所在目录):指定目录去加载,会自动寻找这个目录下所有符合命名规则的测试用例
# run_test.py,与test_register.py、register.py同一目录下
import unittest
import test_register# 第一步,创建一个测试套件
suite = unittest.TestSuite()# 第二步:将测试用例,加载到测试套件中
# 方式1,添加单条测试用例
# case = test_register.TestRegister("test_register_success")    # 创建一个用例对象,注意:通过用例类去创建测试用例对象的时候,需要传入用例的方法名(字符串类型)
# suite.addTest(case)    # 添加用例到测试套件中# 方式2,添加多条测试用例
# case1 = test_register.TestRegister("test_register_success")
# case2 = test_register.TestRegister("test_username_isnull")
# suite.addTest([case1, case2])    # 添加用例到测试套件中# 方式3,添加一个测试用例类
# loader = unittest.TestLoader()    # 创建一个加载对象
# suite.addTest(loader.loadTestsFromTestCase(test_register.TestRegister))# 方式4,添加一个模块
loader = unittest.TestLoader()    # 创建一个加载对象
suite.addTest(loader.loadTestsFromModule(test_register))# 方式5,指定测试用例的所在的目录路径,进行加载
# loader = unittest.TestLoader()
# suite.addTest(loader.discover(r"d:\learn\python"))

通常我们使用方式4、5比较多,你可以根据实际情况来运用。其中方式5,还可以自定义匹配规则,默认是会寻找目录下test*.py文件,即所有以test开头命名的py文件,自定义如下:

loader = unittest.TestLoader()
suite.addTest(loader.discover(start_dir = r"d:\learn\python", pattern="test_case*.py"))        # 匹配规则:所有以test_case开头的

TestRunner执行用例​

test runner顾名思义就是用来执行测试用例的,并且可以生成相应的测试报告。测试报告有两种展示形式,一种是text文本,一种是html格式。

​html格式的就是HTMLTestRunner了,HTMLTestRunner是 Python 标准库的 unittest 框架的一个扩展,它可以生成一个直观清晰的 HTML 测试报告。使用的前提就是要下载 HTMLTestRunner.py,下载完后放在python的安装目录下的scripts目录下即可。

​text文本相对于html来说过于简陋,与控制台输出的没有什么区别,也几乎没有人使用,这里不作演示,使用方法是一样的。我们结合前面的测试套件来演示一下如何生成html格式的测试报告:

# run_test.py,与test_register.py、register.py同一目录下
import unittest
import test_register
from HTMLTestRunner import HTMLTestRunner# 创建测试套件
suite = unittest.TestSuite()# 通过模块加载测试用例
loader = unittest.TestLoader()
suite.addTest(loader.loadTestsFromModule(test_register))# 创建测试运行程序启动器
runner = HTMLTestRunner(stream=open("report.html", "wb"),  # 打开一个报告文件,将句柄传给streamtester="miki",                    # 报告中显示的测试人员description="注册接口测试报告",        # 报告中显示的描述信息title="自动化测试报告")                 # 报告的标题# 使用启动器去执行测试套件里的用例
runner.run(suite)

相关参数说明:

stream:指定输出的方式
tester:报告中要显示的测试人员的名字
description:报告中要显示的面熟信息
title:测试报告的标题
verbosity :表示测试报告信息的详细程度,一共三个值,默认是2

  • 0 (静默模式):你只能获得总的测试用例数和总的结果,如:总共100个 失败10 成功90
  • 1 (默认模式):类似静默模式,只是在每个成功的用例前面有个. 每个失败的用例前面有个F
  • 2 (详细模式):测试结果会显示每个测试用例的所有相关的信息

运行完毕,你会发现你的项目目录下已经生成了一个report.html文件,在浏览器中打开,就可以查看测试报告了。

 最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/3324.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Hugging Face】下载开源大模型步骤

Mac M1 1、国内镜像站 模型基本都可以在国内镜像站 https://hf-mirror.com/ 下载。 部分 Gated Repo 需登录申请许可&#xff0c;需先前往 Hugging Face 官网登录、申请许可&#xff0c;在官网这里获取 Access Token 后回镜像站用命令行下载。 2、注册登陆 Hugging Face 2.1…

STM32的集成开发环境STM32CubeIDE安装

STM32CubeIDE - STM32的集成开发环境 - 意法半导体STMicroelectronics

.Net8 Avalonia跨平台UI框架——<vlc:VideoView>控件播放海康监控、摄像机视频(Windows / Linux)

一、UI效果 二、新建用户控件&#xff1a;VideoViewControl.axaml 需引用&#xff1a;VideoLAN.LibVLC.Windows包 Linux平台需安装&#xff1a;VLC 和 LibVLC &#xff08;sudo apt-get update、sudo apt-get install vlc libvlccore-dev libvlc-dev&#xff09; .axaml 代码 注…

51.WPF应用加图标指南 C#例子 WPF例子

完整步骤&#xff1a; 先使用文心一言生成一个图标如左边使用Windows图片编辑器编辑&#xff0c;去除背景使用正方形&#xff0c;放大图片使图标铺满图片使用格式工程转换为ico格式&#xff0c;分辨率为最大 在资源管理器中右键项目添加ico类型图片到项目里图片属性设置为始终…

C++(二十一)

前言&#xff1a; 本文承接上文&#xff0c;将详细讲解指针概念。 一&#xff0c;通过指针了解变量的数值。 在将变量地址存入指针后&#xff0c;从指针反推也可以知道原变量的值&#xff0c;若想进行反退&#xff0c;就需要使用间接引用运算符&#xff1a;*。 语法&#x…

Redis 性能优化:多维度技术解析与实战策略

文章目录 1 基准性能2 使用 slowlog 优化耗时命令3 big key 优化4 使用 lazy free 特性5 缩短键值对的存储长度6 设置键值的过期时间7 禁用耗时长的查询命令8 使用 Pipeline 批量操作数据9 避免大量数据同时失效10 客户端使用优化11 限制 Redis 内存大小12 使用物理机而非虚拟机…

网络安全面试题汇总(个人经验)

1.谈一下SQL主从备份原理&#xff1f; 答&#xff1a;主将数据变更写入自己的二进制log,从主动去主那里去拉二进制log并写入自己的二进制log,从而自己数据库依据二进制log内容做相应变更。主写从读 2.linux系统中的计划任务crontab配置文件中的五个星星分别代表什么&#xff…

从AI生成内容到虚拟现实:娱乐体验的新边界

引言 在快速发展的科技时代&#xff0c;娱乐行业正经历一场前所未有的变革。传统的娱乐方式正与先进技术融合&#xff0c;创造出全新的沉浸式体验。从AI生成的个性化内容&#xff0c;到虚拟现实带来的身临其境的互动场景&#xff0c;科技不仅改变了我们消费娱乐的方式&#xf…

爬虫基础学习

什么是爬虫: 通过编写程序&#xff0c;模拟浏览器上网&#xff0c;然后让其去互联网上抓取数据的过程。 爬虫的价值: 实际应用就业 爬虫究竟是合法还是违法的? 在法律中是不被禁止具有违法风险善意爬虫 恶意爬虫 爬虫带来的风险可以体现在如下方面: 爬虫干扰了被访问网…

wireshark抓路由器上的包 抓包路由器数据

文字目录 抓包流程概述设置抓包配置选项 设置信道设置无线数据包加密信息设置MAC地址过滤器 抓取联网过程 抓包流程概述 使用Omnipeek软件分析网络数据包的流程大概可以分为以下几个步骤&#xff1a; 扫描路由器信息&#xff0c;确定抓包信道&#xff1b;设置连接路由器的…

第34天:Web开发-PHP应用鉴别修复AI算法流量检测PHP.INI通用过滤内置函数

#知识点 1、安全开发-原生PHP-PHP.INI安全 2、安全开发-原生PHP-全局文件&单函数 3、安全开发-原生PHP-流量检测&AI算法 一、通用-PHP.INI设置 参考&#xff1a; https://www.yisu.com/ask/28100386.html https://blog.csdn.net/u014265398/article/details/109700309 …

Python爬虫学习前传 —— Python从安装到学会一站式服务

早上好啊&#xff0c;大佬们。我们的python基础内容的这一篇终于写好了&#xff0c;啪唧啪唧啪唧…… 说实话&#xff0c;这一篇确实写了很久&#xff0c;一方面是在忙其他几个专栏的内容&#xff0c;再加上生活学业上的事儿&#xff0c;确实精力有限&#xff0c;另一方面&…

【Flink系列】6. Flink中的时间和窗口

6. Flink中的时间和窗口 在批处理统计中&#xff0c;我们可以等待一批数据都到齐后&#xff0c;统一处理。但是在实时处理统计中&#xff0c;我们是来一条就得处理一条&#xff0c;那么我们怎么统计最近一段时间内的数据呢&#xff1f;引入“窗口”。 所谓的“窗口”&#xff…

《汽车维修技师》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答&#xff1a; 问&#xff1a;《汽车维修技师》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《汽车维修技师》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;北方联合出版传媒&#xff08;…

HTML中如何保留字符串的空白符和换行符号的效果

有个字符串 储值门店{{thing3.DATA}}\n储值卡号{{character_string1.DATA}}\n储值金额{{amount4.DATA}}\n当前余额{{amount5.DATA}}\n储值时间{{time2.DATA}} &#xff0c; HTML中想要保留 \n的换行效果的有下面3种方法&#xff1a; 1、style 中 设置 white-space: pre-lin…

Git在码云上的使用指南:从安装到推送远程仓库

目录 目录 前言&#xff1a; 1、git的安装 1.1.Linux-centos环境下安装 1.2.Linux-ubuntu环境下安装 2.创建Git本地仓库 3.配置Git 4.认识⼯作区、暂存区、版本库 5.添加文件 5.1.git命令 5.2.commit命令 6.远程操作 6.1.新建远程仓库 6.2.克隆远程仓库&#xff…

群论学习笔记

什么是对称&#xff1f; 对称是一个保持对象结构不变的变换&#xff0c;对称是一个过程&#xff0c;而不是一个具体的事物&#xff0c;伽罗瓦的对称是对方程根的置换&#xff0c;而一个置换就是对一系列事物的重排方式&#xff0c;严格的说&#xff0c;它也并不是这个重排本身…

联通用户管理系统(一)

#联通用户管理系统&#xff08;一&#xff09; 1.新建项目 如果你是windows的话&#xff0c;界面应该是如下的&#xff1a; 2.创建app python manage.py startapp app01一般情况下&#xff1a;我们是在pycharm的终端中运行上述指令&#xff0c;但是pychrm中为我们提供了工具…

.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上)

系列文章目录 1、.Net Core微服务入门系列&#xff08;一&#xff09;——项目搭建 2、.Net Core微服务入门全纪录&#xff08;二&#xff09;——Consul-服务注册与发现&#xff08;上&#xff09; 3、.Net Core微服务入门全纪录&#xff08;三&#xff09;——Consul-服务注…

在 macOS 上,用命令行连接 MySQL(/usr/local/mysql/bin/mysql -u root -p)

根据你提供的文件内容&#xff0c;MySQL 的安装路径是 /usr/local/mysql。要直接使用 mysql 命令&#xff0c;你需要找到 mysql 可执行文件的路径。 在 macOS 上&#xff0c;mysql 客户端通常位于 MySQL 安装目录的 bin 子目录中。因此&#xff0c;完整的路径应该是&#xff1…