快速看了套Unittest的入门教程
整理一下学习时候整理的内容
目录
3.2 创建测试类,该类继承unittest.TestCase
3.3 定义测试函数,函数名以test_开头,一个函数表示一个用例
创建一个py文件:login_func.py
封装一个函数,用来进行账号密码登录的场景模拟
- # 登录功能场景模拟
- 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的情况下,去设计几个用例
unittest是python自带的一个标准库,不需要进行第三方的安装,可以直接在py文件中导入
创建文件:test_开头
这里是模拟登录,调用封装的登录函数,所以命名可以以:test_login.py 进行命名
import unittest # 导入unittest库
- class TestLogin(unittest.TestCase):
- pass
- class TestLogin(unittest.TestCase):
- def test_login_success(self): # 这个是登录成功的用例函数
- pass
测试数据、期望结果、用例步骤、断言
什么是断言:我们的期望结果与实际结果的比对,而在调用函数后,得到的返回结果也就是实际结果
要调用对应的函数,就需要去把该函数进行一个导入
from login_func import login_check
在类中的方法调用函数
- class TestLogin(unittest.TestCase):
- def test_login_success(self): # 这个是登录成功的用例函数
- # 用例步骤 -- 用例数据
- act_result = login_check("张三", "123456")
调用函数后获取到返回值,这个返回值就是一个实际结果
在获取到实际结果以后,就去比对期望结果与实际结果
在上面的用例中,两个参数传递获取到的期望结果是登录成功的
而实际上,要返回的也应该是这个结果
- class TestLogin(unittest.TestCase):
- def test_login_success(self): # 这个是登录成功的用例函数
- # 用例步骤 -- 用例数据
- act_result = login_check("张三", "123456")
- # 断言 -- 实际结果 与 期望结果的 比对
- if act_result == {"code": 0, "msg": "登录成功"}:
- print("实际与期望相等!")
- else:
- print("实际与期望不相等!")
依次,就可以去照搬去写第二个方法的用例
- def test_login_failed_no_user(self):
- act_result = login_check("李四", "123456")
-
- # 断言 -- 实际结果 与 期望结果的 比对
- if act_result == {"code": 1, "msg": "登录失败了"}:
- print("实际与期望相等!")
- else:
- print("实际与期望不相等!")
(用户名错误、密码正确的情况)
在上述的测试用例中,期望结果与实际结果的比对中是用if来进行
这个if与断言又有什么取别?
以上述的用例,在账号密码正确的用例中,修改if判断中比对的字典中,msg属性的值
if act_result == {"code": 0, "msg": "登录成功!!!"}:
进行测试的结果,执行的结果仍然是以用例通过为标准,但是输出的结果并不符合预期
那么,在if这个模式下,unittest并不会认为我们的用例是失败的
如果,把if转换成unittest的断言,应该如何编写
注释掉代码块中的if代码块
使用:self.assert....
在当前的用例中,需求是需要去比对:期望与返回值结果相等,所以使用断言:
self.assertEqual(self,first,second)
参数1:期望结果
参数2:实际结果
参数3:当实际与期望不相等时,自己希望得到的提示
修改代码
- def test_login_success(self): # 这个是登录成功的用例函数
- # 用例步骤 -- 用例数据
- act_result = login_check("张三", "123456")
- print(act_result)
- # 断言 -- 实际结果 与 期望结果的 比对
- expected = {"code": 0, "msg": "登录成功!!!"}
- self.assertEqual(expected, act_result, "实际与期望不相等!!!")
测试的结果可以得到一个很明确的报错
AssertionError:断言错误
方法 | 检查 | 说明 |
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的实例,则断言通过 |
测试用例的准备工作;setup()
测试用例的清理工作:teardown()
在测试类中,def两个方法,一个是setup()、一个是tearDown()
- def setUp(self) -> None:
- print("我是准备工作....")
-
- def tearDown(self) -> None:
- print("我是清理工作")
此时,执行测试用例
用例是被夹子的准备工作和清除工作夹在中间,每一个用例都会被执行
setup()先执行 --> 测试用例 --> tearDown()
测试类的准备工作:setupClass()
测试类的清理工作:teardownClass()
注意:在方法名上要加注解 @classmethod
- @classmethod
- def setUpClass(cls) -> None:
- print("class 我是准备工作....")
-
- @classmethod
- def tearDownClass(cls) -> None:
- print("class 我是清理工作")
执行顺序:setUpClass() --> 用例1--> 用例2 --> tearDownClass()
需求:在前置工作中的数据,如何作为参数传递给测试用例?
现在有一个测试类的夹具和一个测试方法夹具
- @classmethod
- def setUpClass(cls) -> None:
- print("class 我是准备工作....")
-
- def setUp(self) -> None:
- print("我是准备工作....")
那么,就需要设置成实例属性、类属性
- def setUp(self) -> None:
- print("我是准备工作....")
- self.name = "张三"
- self.pwd = "123456"
在方法中添加两个实例
在测试用例中,通过:self.xxx的方式访问
- def test_login_success(self): # 这个是登录成功的用例函数
- act_result = login_check("张三", "123456")
- print("从前置当中获取准备好的变量")
- print(self.name)
- print(self.pwd)
在这个前置方法中,需要定义为类属性
- @classmethod
- def setUpClass(cls) -> None:
- print("class 我是准备工作....")
- # cls.class_xxxx
- cls.class_name = "class_张三"
在测试用例的方法中,也直接通过self.变量名访问
- print("从class前置当中获取准备好的变量")
- print(self.class_name)
测试用例会分布在不同的包、不同的模块(.py)中。
我们需要收集所有的测试用例,一并执行,并生成测试报告。
使用TestLoader类收集用例,加载到TestSuite类当中,最后执行TTestSuite中的用例即可
需求:把两个py文件中的测试用例,收集起来后,统一运行
代码:(把原有的py文件中,另外一个测试用例剪切到第二个py文件中)
模拟登录成功的py文件1
- 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
- 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, "实际与期望不相等!!!")
unittest.TestSuite()
添加用例到套件中的方法:
第一步:独立创建一个py文件:main.py
导入:unittest
实例化TestSuite()
- import unittest
-
- s = unittest.TestSuite()
实例化之后,调用addTest()方法,而参数是我们的测试用例,但是测试用例分布在其他的文件当中,所以需要对测试用例的类进行一个导入
- # 导入测试用例
- from test01 import TestLogin
语法格式:addTest(类名("用例名")
- 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)}")
格式:addTests([测试类名("用例名"),测试类名("用例名")])
- 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加载用例
可以通过:类名、模块名、目录 三种方式去收集用例到测试套件中
在自动化的时候,用例会集中的写在某一个py包下面,所以对目录是比较具体化的
在目录下,会添加多个用例
所以通过指定的目录,去默认搜索全部的测试用例
但是在目录下面有很多个py文件,每个py文件也不一定都是测试用例
所以,需要对文件名也要有一定的过滤
默认:如果py的文件名以:test*.py开头,就会对这些文件进行搜索
Code
- import unittest
-
- # 1、实例化load
- load = unittest.TestLoader()
- # 2、使用discover方法:搜索指定目录,用变量接收结果
- s = load.discover("test01")
- # 3、打印出的s很长,可以用for循环来阅读
- for i in s:
- print(i)
- print("\n")
格式:unittest.TestLoader().loadTestsFromTestCase(测试类名)
- 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
- # 3、实例化测试套件
- s = unittest.TestSuite()
- s.addTests([s1, s2])
- print(s)
格式:unittest.TestLoader().loadTestsFromModule(模块名)
也需要先导入模块,这里就不是导入类了
- 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 · PyPIhttps://pypi.org/project/unittestreport/
安装命令(支持py3.6+):pip install -U unittestreport
report库提供了三种不同的HTML测试报告样式
准备代码
- 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
from unittestreport import TestRunner
2、收集用例
- # 1、实例化load
- load = unittest.TestLoader()
- # 2、使用discover方法:搜索指定目录,用变量接收结果
- s = load.discover("test01")
3、运行用例,生成报告
- # 3、运行用例,生成报告
- runner = TestRunner(s)
4、执行run()方法,生成报告文件
- # 4、执行run方法,生成报告
- runner.run()
5、代码
- 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()
关于TestRunner初始化参数
在使用TestRunner创建测试运行程序时,可以通过以下参数来,自定义报告的相关内容
suites: 测试套件(必传)
filename: 指定报告文件名
report_dir:指定存放报告路径
title:指定测试报告的标题
templates: 可以指定1,2,3三个风格的模板
tester:测试人员名称
1、修改TestRunner()方法的参数
- # 3、运行用例,生成报告
- runner = TestRunner(s,
- filename='模拟登录测试',
- title="ChangFeng的测试报告",
- templates=2,
- tester='长风沛雨'
- )
完整点的代码
- 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(data driven test) 数据驱动测试的设计思想
应用场景
同一个流程,多组数据形成多条用例
Python第三方库:
使用 unittestreport库的ddt模块
ddt、json_data、list_data、yaml_data
注意点:测试报告中,用例的描述信息设置(title或者desc字段或者用例文档注释)
本文使用的是:unittestreport的数据驱动
举例使用:
1、创建一个文件来存储数据
创建文件:data.py
这里提供5组数据
- 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的方法
- 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
引入
from unittestreport import ddt, list_data
在类上装饰ddt
- @ddt
- class TestLoginPython(unittest.TestCase):
4、定义、接收测试数据
4.1 导入数据
from data import all_case_data
4.2 在用例方法上,把数据传给注解@list_data(),并在方法内用一个参数进行接收
- @ddt
- class TestLoginPython(unittest.TestCase):
- @list_data(all_case_data)
- def test_login(self, case):
而参数case,是接收到的每一组字典,那么,输入的数据就不用写死,只需要用到字典中的属性即可
4.3 调用数据
- @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、该文件完整代码
- 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文件,生成测试报告
前面已经是写过了,不需要再修改了
- 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()
执行一下
如果需要用json_data来进行测试,就需要修改数据的格式,在前面8.1中,使用的是python的数据格式,这个json_data则需要修改为json格式
json与python数据类型的注意事项
json的写法:
字符串是双引号的
使用 null 来表示python中的 None
使用 true 和 false 表示布尔值
1、新建data.json文件
键入json类型的数据
- [
- {
- "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的相对路径或者绝对路径
- 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, "实际与期望不相等!!!")