快速看了套Unittest的入门教程
整理一下学习时候整理的内容
目录
[3.1 导入unittest模块](#3.1 导入unittest模块)
[3.2 创建测试类,该类继承unittest.TestCase](#3.2 创建测试类,该类继承unittest.TestCase)
[3.3 定义测试函数,函数名以test_开头,一个函数表示一个用例](#3.3 定义测试函数,函数名以test_开头,一个函数表示一个用例)
[3.4 编写用例](#3.4 编写用例)
[3.5 普通的比对表达(if)](#3.5 普通的比对表达(if))
[4.1 断言方法](#4.1 断言方法)
[五、Fixture 夹具](#五、Fixture 夹具)
[5.1 测试用例的夹具](#5.1 测试用例的夹具)
[5.2 测试类的夹具](#5.2 测试类的夹具)
[5.3 前置工作的数据传递给测试用例](#5.3 前置工作的数据传递给测试用例)
[5.3.1 setUp() 实例属性](#5.3.1 setUp() 实例属性)
[5.3.2 setUpClass() 类属性](#5.3.2 setUpClass() 类属性)
[6.1 TestSuite套件](#6.1 TestSuite套件)
[6.1.1 addTest()方法](#6.1.1 addTest()方法)
[6.1.2 addTests()方法](#6.1.2 addTests()方法)
[6.2 TestLoader 加载用例](#6.2 TestLoader 加载用例)
[6.2.1 目录 discover](#6.2.1 目录 discover)
[6.2.2 从测试类中加载](#6.2.2 从测试类中加载)
[6.2.3 从模块名中加载](#6.2.3 从模块名中加载)
[七、unittestreport - 生成HTML报告](#七、unittestreport - 生成HTML报告)
[7.1 初始化参数](#7.1 初始化参数)
[8.1 list_data](#8.1 list_data)
[8.2 json_data](#8.2 json_data)
[8.3 yaml](#8.3 yaml)
一、用例编写规则
- 1、导入 unittest模块,被测文件或者其中的类
- 2、创建一个测试类,并继承unittest.TestCase
- 3、定义测试函数,函数名以 test_开头(表示一个测试用例)
- 4、测试用例中:
- 4.1 测试数据、期望结果
- 4.2 用例步骤
- 4.3 断言
二、模拟静态的登录数据
创建一个py文件:login_func.py
封装一个函数,用来进行账号密码登录的场景模拟
python
# 登录功能场景模拟
def login_check(userName=None, passWord=None):
if userName is not None and passWord is not None:
if userName == "张三" and passWord == '123456':
return {"code": 0, "msg": "登录成功"}
else:
return {"code": 1, "msg": "登录失败了"}
else:
return {"code": 1, "msg": "所有的参数不能为空"}
然后基于账号为张三,密码为123456的情况下,去设计几个用例
三、编写用例
3.1 导入unittest模块
unittest是python自带的一个标准库,不需要进行第三方的安装,可以直接在py文件中导入
创建文件:test_开头
这里是模拟登录,调用封装的登录函数,所以命名可以以:test_login.py 进行命名
python
import unittest # 导入unittest库
3.2 创建测试类,该类继承unittest.TestCase
python
class TestLogin(unittest.TestCase):
pass
3.3 定义测试函数,函数名以test_开头,一个函数表示一个用例
python
class TestLogin(unittest.TestCase):
def test_login_success(self): # 这个是登录成功的用例函数
pass
3.4 编写用例
测试数据、期望结果、用例步骤、断言
什么是断言:我们的期望结果与实际结果的比对,而在调用函数后,得到的返回结果也就是实际结果
要调用对应的函数,就需要去把该函数进行一个导入
python
from login_func import login_check
在类中的方法调用函数
python
class TestLogin(unittest.TestCase):
def test_login_success(self): # 这个是登录成功的用例函数
# 用例步骤 -- 用例数据
act_result = login_check("张三", "123456")
调用函数后获取到返回值,这个返回值就是一个实际结果
3.5 普通的比对表达(if)
在获取到实际结果以后,就去比对期望结果与实际结果
在上面的用例中,两个参数传递获取到的期望结果是登录成功的
而实际上,要返回的也应该是这个结果
python
class TestLogin(unittest.TestCase):
def test_login_success(self): # 这个是登录成功的用例函数
# 用例步骤 -- 用例数据
act_result = login_check("张三", "123456")
# 断言 -- 实际结果 与 期望结果的 比对
if act_result == {"code": 0, "msg": "登录成功"}:
print("实际与期望相等!")
else:
print("实际与期望不相等!")
依次,就可以去照搬去写第二个方法的用例
python
def test_login_failed_no_user(self):
act_result = login_check("李四", "123456")
# 断言 -- 实际结果 与 期望结果的 比对
if act_result == {"code": 1, "msg": "登录失败了"}:
print("实际与期望相等!")
else:
print("实际与期望不相等!")
(用户名错误、密码正确的情况)
四、断言assert
在上述的测试用例中,期望结果与实际结果的比对中是用if来进行
这个if与断言又有什么取别?
以上述的用例,在账号密码正确的用例中,修改if判断中比对的字典中,msg属性的值
python
if act_result == {"code": 0, "msg": "登录成功!!!"}:
进行测试的结果,执行的结果仍然是以用例通过为标准,但是输出的结果并不符合预期
那么,在if这个模式下,unittest并不会认为我们的用例是失败的
如果,把if转换成unittest的断言,应该如何编写
注释掉代码块中的if代码块
使用:self.assert....
在当前的用例中,需求是需要去比对:期望与返回值结果相等,所以使用断言:
self.assertEqual(self,first,second)
参数1:期望结果
参数2:实际结果
参数3:当实际与期望不相等时,自己希望得到的提示
修改代码
python
def test_login_success(self): # 这个是登录成功的用例函数
# 用例步骤 -- 用例数据
act_result = login_check("张三", "123456")
print(act_result)
# 断言 -- 实际结果 与 期望结果的 比对
expected = {"code": 0, "msg": "登录成功!!!"}
self.assertEqual(expected, act_result, "实际与期望不相等!!!")
测试的结果可以得到一个很明确的报错
AssertionError:断言错误
4.1 断言方法
|--------------------------|---------------------|----------------------------------------------------------------------------|
| 方法 | 检查 | 说明 |
| assertEqual(a,b) | a == b | 检查变量a和b是否相等。如果不相等,测试将失败。 |
| assertNotEqual(a,b) | a != b | 检查变量a和b是否不相等。如果相等,测试将失败。 |
| assertTrue(x) | bool(x) is True | 参数可以为表达式、其他函数的调用,如果结果是true,则断言成功,false则失败 |
| assertFalse(x) | Bool(x) is False | 参数可以为表达式、其他函数的调用,如果结果是false,则断言成功,true则失败 |
| assertIs(a,b) | a is b | is比较两个变量的内存地址是否一致,如一致,为true,断言成功,反之失败 |
| assertIsNot(a,b) | a is not b | is比较两个变量的内存地址是否一致,如不一致,断言成功,反之一致则断言失败 |
| assertIsNone(x) | x is None | 比较变量x是否是None,如果是,则断言成功,反之失败 |
| assertIsNotNone(x) | x is no None | 比较变量x是否是None,如果不是,则断言成功,反之失败 |
| assertIn(a,b) | a in b | 比较a成员是否在b容器(字符串、列表、元组、元组、集合等)中的一员,如果是,则断言成功,反之失败 |
| assertNotIn(a,b) | a not in b | 比较a成员是否在b容器(字符串、列表、元组、元组、集合等)中的一员,如果不是,则断言成功,反之失败 |
| assertIsInstance(a,b) | isinstance(a,b) | 比较a是否是b的实例,如果属于b的实例,则断言通过 例如:变量a="hello",去判断变量a是否是str的实例,返回的结果是true,就断言成功了 |
| assertNotIsInstance(a,b) | not isinstance(a,b) | 比较a是否是b的实例,如果不属于b的实例,则断言通过 |
| | | |
五、Fixture 夹具
- 测试用例 级别的夹具
- 测试用例的准备工作;setup()
- 测试用例的清理工作:teardown()
- 测试类 级别的夹具:
- 测试类的准备工作:setupClass()
- 测试类的清理工作:teardownClass()
- 注意:在方法名上要加注解 @classmethod
- 前置中的 数据传递 到测试用例当中去:
- 通过设置实例属性、类属性来传递
5.1 测试用例的夹具
测试用例的准备工作;setup()
测试用例的清理工作:teardown()
在测试类中,def两个方法,一个是setup()、一个是tearDown()
python
def setUp(self) -> None:
print("我是准备工作....")
def tearDown(self) -> None:
print("我是清理工作")
此时,执行测试用例
用例是被夹子的准备工作和清除工作夹在中间,每一个用例都会被执行
setup()先执行 --> 测试用例 --> tearDown()
5.2 测试类的夹具
测试类的准备工作:setupClass()
测试类的清理工作:teardownClass()
注意:在方法名上要加注解 @classmethod
python
@classmethod
def setUpClass(cls) -> None:
print("class 我是准备工作....")
@classmethod
def tearDownClass(cls) -> None:
print("class 我是清理工作")
执行顺序:setUpClass() --> 用例1--> 用例2 --> tearDownClass()
5.3 前置工作的数据传递给测试用例
需求:在前置工作中的数据,如何作为参数传递给测试用例?
现在有一个测试类的夹具和一个测试方法夹具
python
@classmethod
def setUpClass(cls) -> None:
print("class 我是准备工作....")
def setUp(self) -> None:
print("我是准备工作....")
那么,就需要设置成实例属性、类属性
5.3.1 setUp() 实例属性
python
def setUp(self) -> None:
print("我是准备工作....")
self.name = "张三"
self.pwd = "123456"
在方法中添加两个实例
在测试用例中,通过:self.xxx的方式访问
python
def test_login_success(self): # 这个是登录成功的用例函数
act_result = login_check("张三", "123456")
print("从前置当中获取准备好的变量")
print(self.name)
print(self.pwd)
5.3.2 setUpClass() 类属性
在这个前置方法中,需要定义为类属性
python
@classmethod
def setUpClass(cls) -> None:
print("class 我是准备工作....")
# cls.class_xxxx
cls.class_name = "class_张三"
在测试用例的方法中,也直接通过self.变量名访问
python
print("从class前置当中获取准备好的变量")
print(self.class_name)
六、收集用例
测试用例会分布在不同的包、不同的模块(.py)中。
我们需要收集所有的测试用例,一并执行,并生成测试报告。
- TestSuite类:测试用例集合
- TestLoader类:测试用例收集
使用TestLoader类收集用例,加载到TestSuite类当中,最后执行TTestSuite中的用例即可
需求:把两个py文件中的测试用例,收集起来后,统一运行
代码:(把原有的py文件中,另外一个测试用例剪切到第二个py文件中)
模拟登录成功的py文件1
python
import unittest
from login_func import login_check
class TestLogin(unittest.TestCase):
def test_login_success(self): # 这个是登录成功的用例函数
act_result = login_check("张三", "123456")
expected = {"code": 0, "msg": "登录成功"}
self.assertEqual(expected, act_result, "实际与期望不相等!!!")
模拟登录失败的py文件2
python
import unittest
from login_func import login_check
class TestNoLogin(unittest.TestCase):
def test_login_failed_no_user(self):
act_result = login_check("李四", "123456")
expected = {"code": 1, "msg": "登录失败了"}
self.assertEqual(expected, act_result, "实际与期望不相等!!!")
6.1 TestSuite套件
unittest.TestSuite()
添加用例到套件中的方法:
- 方法一:addTest(测试类名("用例名"))
- 添加一个测试用例
- 每次只能添加一个
- 方法二:addTests([测试类名("用例名"),测试类名("用例名")])
- 添加一个用例的列表
第一步:独立创建一个py文件:main.py
导入:unittest
实例化TestSuite()
python
import unittest
s = unittest.TestSuite()
6.1.1 addTest()方法
实例化之后,调用addTest()方法,而参数是我们的测试用例,但是测试用例分布在其他的文件当中,所以需要对测试用例的类进行一个导入
python
# 导入测试用例
from test01 import TestLogin
语法格式:addTest(类名("用例名")
python
import unittest
# 导入测试用例
from test_login import TestLogin
from test_no_login import TestNoLogin
s = unittest.TestSuite()
s.addTest(TestLogin("test_login_success"))
s.addTest(TestNoLogin("test_login_failed_no_user"))
print(f"实例{s},类型{type(s)}")
6.1.2 addTests()方法
格式:addTests([测试类名("用例名"),测试类名("用例名")])
python
import unittest
# 导入测试用例
from test_login import TestLogin
from test_no_login import TestNoLogin
s = unittest.TestSuite()
s.addTests([TestLogin("test_login_success"),
TestNoLogin("test_login_failed_no_user")])
print(f"实例{s},类型{type(s)}")
但是无论是addTest还是addTests,如果在用例过多的情况下,书写是非常麻烦的
所以,有了一个新的东西:TestLoader加载用例
6.2 TestLoader 加载用例
可以通过:类名、模块名、目录 三种方式去收集用例到测试套件中
- 方法一:目录(常用)
- unittest.TestLoader().discover(搜索目录)
- 默认在:test*.py 中搜索用例
- unittest.TestLoader().discover(搜索目录)
- 方法二:类名
- unittest.TestLoader().loadTestsFromTestCase(测试类名)
- 注意:测试类名不需要加引号
- unittest.TestLoader().loadTestsFromTestCase(测试类名)
- 方法三:模块名
- unittest.TestLoader().loadTestsFromModule(模块名)
- 注意:模块名不需要加入引号(需要要导入模块)
- unittest.TestLoader().loadTestsFromModule(模块名)
6.2.1 目录 discover
在自动化的时候,用例会集中的写在某一个py包下面,所以对目录是比较具体化的
在目录下,会添加多个用例
所以通过指定的目录,去默认搜索全部的测试用例
但是在目录下面有很多个py文件,每个py文件也不一定都是测试用例
所以,需要对文件名也要有一定的过滤
默认:如果py的文件名以:test*.py开头,就会对这些文件进行搜索
Code
python
import unittest
# 1、实例化load
load = unittest.TestLoader()
# 2、使用discover方法:搜索指定目录,用变量接收结果
s = load.discover("test01")
# 3、打印出的s很长,可以用for循环来阅读
for i in s:
print(i)
print("\n")
6.2.2 从测试类中加载
格式:unittest.TestLoader().loadTestsFromTestCase(测试类名)
python
import unittest
from test_login import TestLogin
from test_no_login import TestNoLogin
# 1、实例化load
load = unittest.TestLoader()
# 2、从测试类中加载(在文件顶部需要先导入测试类)
s1 = load.loadTestsFromTestCase(TestLogin)
s2 = load.loadTestsFromTestCase(TestNoLogin)
这个时候有两个类,然后分开导入就有了两个套件
但是需求:把两个套件合并到一个套件里面去
把s1和s2合并到addTests
python
# 3、实例化测试套件
s = unittest.TestSuite()
s.addTests([s1, s2])
print(s)
6.2.3 从模块名中加载
格式:unittest.TestLoader().loadTestsFromModule(模块名)
也需要先导入模块,这里就不是导入类了
python
import unittest
# 导入两个模块
import test_login
import test_no_login
# 1、实例化load
load = unittest.TestLoader()
# 2、从模块名当中进行导入
s1 = load.loadTestsFromModule(test_login)
s2 = load.loadTestsFromModule(test_no_login)
# 3、实例化测试套件
s = unittest.TestSuite()
s.addTests([s1, s2]) # 合并到大套件
print(s)
七、unittestreport - 生成HTML报告
使用unittestreport第三方库生成不同的HTML报告
第三方库地址:unittestreport · PyPIhttps://pypi.org/project/unittestreport/
安装命令(支持py3.6+):pip install -U unittestreport
report库提供了三种不同的HTML测试报告样式
准备代码
python
import unittest
# 1、实例化load
load = unittest.TestLoader()
# 2、使用discover方法:搜索指定目录,用变量接收结果
s = load.discover("test01")
开始进行操作:
参考文档地址:二、HTML测试报告生成 - unittestreport 使用文档https://unittestreport.readthedocs.io/en/latest/doc2_report/
1、导入TestRunner
python
from unittestreport import TestRunner
2、收集用例
python
# 1、实例化load
load = unittest.TestLoader()
# 2、使用discover方法:搜索指定目录,用变量接收结果
s = load.discover("test01")
3、运行用例,生成报告
python
# 3、运行用例,生成报告
runner = TestRunner(s)
4、执行run()方法,生成报告文件
python
# 4、执行run方法,生成报告
runner.run()
5、代码
python
import unittest
from unittestreport import TestRunner
# 1、实例化load
load = unittest.TestLoader()
# 2、使用discover方法:搜索指定目录,用变量接收结果
s = load.discover("test01")
# 3、运行用例,生成报告
runner = TestRunner(s)
# 4、执行run方法,生成报告
runner.run()
7.1 初始化参数
关于TestRunner初始化参数
在使用TestRunner创建测试运行程序时,可以通过以下参数来,自定义报告的相关内容
suites: 测试套件(必传)
filename: 指定报告文件名
report_dir:指定存放报告路径
title:指定测试报告的标题
templates: 可以指定1,2,3三个风格的模板
tester:测试人员名称
1、修改TestRunner()方法的参数
python
# 3、运行用例,生成报告
runner = TestRunner(s,
filename='模拟登录测试',
title="ChangFeng的测试报告",
templates=2,
tester='长风沛雨'
)
完整点的代码
python
import unittest
from unittestreport import TestRunner
# 1、实例化load
load = unittest.TestLoader()
# 2、使用discover方法:搜索指定目录,用变量接收结果
s = load.discover("test01")
# 3、运行用例,生成报告
runner = TestRunner(s,
filename='模拟登录测试',
title="ChangFeng的测试报告",
templates=2,
tester='长风沛雨'
)
# 4、执行run方法,生成报告
runner.run()
templates属性值的可选项为【1,2,3】对应了三种不同的样式风格
八、ddt数据驱动
ddt(data driven test) 数据驱动测试的设计思想
应用场景
同一个流程,多组数据形成多条用例
Python第三方库:
使用 unittestreport库的ddt模块
ddt、json_data、list_data、yaml_data
注意点:测试报告中,用例的描述信息设置(title或者desc字段或者用例文档注释)
本文使用的是:unittestreport的数据驱动
8.1 list_data
举例使用:
1、创建一个文件来存储数据
创建文件:data.py
这里提供5组数据
python
all_case_data = [
# 以字典存储,用户名、密码、期望结果
{
"title": "登录成功",
"userName": "张三",
"passWord": "123456",
"msg": {"code": 0, "msg": "登录成功"}
},
{
"title": "密码错误",
"userName": "张三",
"passWord": "123456789",
"msg": {"code": 1, "msg": "登录失败了"}
},
{
"title": "账号错误",
"userName": "李四",
"passWord": "123456",
"msg": {"code": 1, "msg": "登录失败了"}
},
{
"title": "账号为空",
"userName": None,
"passWord": "123456",
"msg": {"code": 1, "msg": "所有的参数不能为空"}
},
{
"title": "密码为空",
"userName": "张三",
"passWord": None,
"msg": {"code": 1, "msg": "所有的参数不能为空"}
}
]
2、创建一个test_use_ddt.py文件
新建的该文件是用来运行上面的数据来跑用例
然后创建一个类,该类基础TestCase,调用login_fuc的方法
python
import unittest
from login_func import login_check
class TestLoginPython(unittest.TestCase):
def test_login(self):
# 导入login_check()的方法
act_result = login_check("张三", "123456")
expected = {"code": 0, "msg": "登录成功"}
self.assertEqual(expected, act_result, "实际与期望不相等!!!")
3、引入、装饰ddt
引入
python
from unittestreport import ddt, list_data
在类上装饰ddt
python
@ddt
class TestLoginPython(unittest.TestCase):
4、定义、接收测试数据
4.1 导入数据
python
from data import all_case_data
4.2 在用例方法上,把数据传给注解@list_data(),并在方法内用一个参数进行接收
python
@ddt
class TestLoginPython(unittest.TestCase):
@list_data(all_case_data)
def test_login(self, case):
而参数case,是接收到的每一组字典,那么,输入的数据就不用写死,只需要用到字典中的属性即可
4.3 调用数据
python
@ddt
class TestLoginPython(unittest.TestCase):
@list_data(all_case_data)
def test_login(self, case):
# 导入login_check()的方法
act_result = login_check(case.get("userName"), case.get("passWord"))
self.assertEqual(case.get("msg"), act_result, "实际与期望不相等!!!")
数据获取语法:字典.get("属性")
5、该文件完整代码
python
import unittest
from login_func import login_check
from unittestreport import ddt, list_data
from data import all_case_data
@ddt
class TestLoginPython(unittest.TestCase):
@list_data(all_case_data)
def test_login(self, case):
# 导入login_check()的方法
act_result = login_check(case.get("userName"), case.get("passWord"))
self.assertEqual(case.get("msg"), act_result, "实际与期望不相等!!!")
然后可以测试运行一下
一共有5条测试用例
而在用例的测试后缀中是添加了001开始的编号
但是,在测试的结果、测试的报告中,希望可以看到每个用例的含义
这个时候,在前面data.py文件中,字典里面的title属性就有了用处
6、返回main.py文件,生成测试报告
前面已经是写过了,不需要再修改了
python
import unittest
from unittestreport import TestRunner
# 1、实例化load
load = unittest.TestLoader()
# 2、使用discover方法:搜索指定目录,用变量接收结果
s = load.discover("test01")
# 3、运行用例,生成报告
runner = TestRunner(s,
filename='模拟登录测试',
title="ChangFeng的测试报告",
templates=2,
tester='长风沛雨'
)
# 4、执行run方法,生成报告
runner.run()
执行一下
8.2 json_data
如果需要用json_data来进行测试,就需要修改数据的格式,在前面8.1中,使用的是python的数据格式,这个json_data则需要修改为json格式
json与python数据类型的注意事项
json的写法:
字符串是双引号的
使用 null 来表示python中的 None
使用 true 和 false 表示布尔值
1、新建data.json文件
键入json类型的数据
python
[
{
"title": "登录成功",
"userName": "张三",
"passWord": "123456",
"msg": {"code": 0, "msg": "登录成功"}
},
{
"title": "密码错误",
"userName": "张三",
"passWord": "123456789",
"msg": {"code": 1, "msg": "登录失败了"}
},
{
"title": "账号错误",
"userName": "李四",
"passWord": "123456",
"msg": {"code": 1, "msg": "登录失败了"}
},
{
"title": "账号为空",
"userName": null,
"passWord": "123456",
"msg": {"code": 1, "msg": "所有的参数不能为空"}
},
{
"title": "密码为空",
"userName": "张三",
"passWord": null,
"msg": {"code": 1, "msg": "所有的参数不能为空"}
}
]
2、读取json数据
2.1 同样需要引入json.data
2.2 同样需要在用例上注解@json
2.3 注解方法传入的参数是json的相对路径或者绝对路径
python
import unittest
from login_func import login_check
from unittestreport import ddt, json_data
@ddt
class TestLoginPython(unittest.TestCase):
@json_data(r"C:\Users\13195\Desktop\3ban_py\unittest\test01\data.json")
def test_login(self, case):
# 导入login_check()的方法
act_result = login_check(case.get("userName"), case.get("passWord"))
self.assertEqual(case.get("msg"), act_result, "实际与期望不相等!!!")