UnitTest
- 一、UnitTest 基本使用
- 1. TestCase 测试用例
- 2. TestSuite 和 TestRunner
- 3. TestLoader 测试加载
- 4. Fixture
- 二、断言与参数化
- 断言
- 参数化
- 三、测试报告
- 获取项目的绝对路径
- 登录案例
- 跳过
一、UnitTest 基本使用
- UnItTest 框架介绍
UnitTest是python自带的一个单元测试框架,测试人员也经常用来做自动化测试,用来管理和执行用例 - 使用的原因:
能够组织多个用例去执行
提供丰富的断言方法
能够生成测试报告 - 核心要素
1. TestCase 测试用例,这个测试用例是unittest的组成部分,作用是用来书写真正的用例代码(脚本)
2. Testsuite 测试套件,作用是用来组装(打包)Testcase的,即可以将多个用例脚本文件组装到一起
3. TestRunner 测试执行,作用是用例执行 Testsuite(测试套件)的
4. TestLoader 测试加载,是对Testsuite (测试套件)功能的补充,作用是用来组装(打包)Testcase的
5. Fixture 测试夹具,是一种代码结构,书写前置方法(执行用例之前的方法)代码和后置方法(执行用例之后的方法)代码
1. TestCase 测试用例
书写真正的用例代码(脚本)
- 步骤
- 导包 unnitest:
import unittest
- 定义测试类,需要
继承unittest.TestCase
类,习惯性类名以Test开头 - 书写测试方法,
必须以test开头
- 执行
- 导包 unnitest:
- 注意事项
代码的文件名字,需要满足标识符的规则
'''
TestCase 测试用例的使用
'''
import unittest
# 继承unittest.TestCase类,就是测试类
class TestDemo(unittest.TestCase):
# 书写测试方法,方法名必须以test开头
def test_method1(self):
print("测试方法1")
def test_method2(self):
print("测试方法2")
# 执行 在类名或者方法名后右键运行
# 在主程序使用 unittest.main()来执行
if __name__ == '__main__':
unittest.main()
2. TestSuite 和 TestRunner
- TestSuite (测试套件)
将多条用例脚本集合在一起,就是套件,即用来组装用例的
- 步骤
– 导包 unittest
– 实例化套件对象 unittest.TestSuite()
– 添加用例方法
- TestRunner (测试执行)
- 步骤
– 导包 unittest
– 实例化执行对象 unittest.TextTestRunner()
– 执行对象执行套件对象执行对象.run(套件对象)
import unittest
# 实例化套件对象
from day0610.testcase import TestDemo
# 实例化套件对象
suite = unittest.TestSuite()
# 添加用例方法
# 套件对象.addTest(测试类名('测试方法名'))
suite.addTest(TestDemo('test_method1'))
suite.addTest(TestDemo('test_method2'))
# 添加整个测试类
# 套件对象.addTest(unittest.makeSuite('测试类名'))
suite.addTest(unittest.makeSuite(TestDemo))
# 实例化执行对象
runner = unittest.TextTestRunner()
# 执行对象执行 执行对象.run(套件对象)
runner.run(suite)
3. TestLoader 测试加载
作用和TestSuite一样,组装用例代码,同样也需要TextTestRunner()去执行
import unittest
# 实例化加载对象并加载用例,得到套件对象
# suite = unittest.TestLoader().discover('用例所在的目录','用例代码文件名.py')
suite = unittest.TestLoader().discover('.','testcase.py')
# 实例化执行对象并执行
# runner = unittest.TextTestRunner()
# runner.run(suite)
unittest.TextTestRunner().run(suite)
- TestLoader 和 TestSuite的区别
- TestSuite
灵活,方便控制要执行的测试用例,但是需要手动添加测试用例 - TestLoader
可以自动搜索满足条件的测试用例,不方便控制要执行的测试用例
- TestSuite
4. Fixture
在用例执行前后会自动执行的代码结构
- 方法级别 Fixture
在每个用例执行前后都会自动调用,方法名是固定的
# 每个用例执行前都会自动调用
def setUp(self): # 前置
pass
# 每个用例执行后都会自动调用
def tearDown(self): # 后置
pass
- 类级别 Fixture
在类中所有的测试方法执行前后,会自动执行的代码,只执行一次
@classmethod
def setUpClass(cls): # 类前置
pass
@classmethod
def tearDownClass(cls): # 类后置
pass
- 模块级别 Fixture(了解)
在代码文件执行前后执行一次
def setUpModule():
pass
def tearDownModule():
pass
二、断言与参数化
跳过:某些用例由于某种原因不想执行,设置为跳过
生成测试报告:suite和runner(第三方)
断言
断言:使用代码自动地判断预期结果和实际结果是否相符
- assertEqual(预期结果,实际结果)
判断预期结果和实际结果是否相等,如果相等,用例通过;如果不相等,抛出异常,用例不通告 - assertIn(预期结果,实际结果)
判断预期结果是否包含在实际结果中,如果存在,用例通过;如果不存在,抛出异常,用例不通告
import unittest
class TestAssert(unittest.TestCase):
def test_equal_1(self):
self.assertEqual(10, 10) # 用例通过
def test_equal_2(self):
self.assertEqual(10, 11) # 用例不通过
def test_in(self):
self.assertIn('admin', '欢迎admin登录') # 用例通过
- 使用suite来运行
import unittest
from day0610.demo4 import TestAssert
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestAssert))
unittest.TextTestRunner().run(suite)
参数化
参数化:将测试数据定义到json文件,使用
- 通过参数的方式来传递数据,从而实现数据和脚本分离,并且可以实现用例的重复执行(在书写用例方法时,测试数据使用变量代替,在执行的时候进行数据传递)
- unittest测试框架,本身不支持参数化,但是可以通过安装unittest扩展插件parameterized来实现
- 环境准备
因为参数化的插件,不是unittst自带的,所以想要使用需要进行安装,python中的包(插件、模块)的安装,使用 pip 工具
pip install parameterized
# 在cmd终端中执行
tips: pip install -i https://pypi.douban.com/simple/ parameterized 可以临时指定pip下载源 - 使用
1.导包 from para… import para…
2.修改测试方法,将测试方法中的测试数据使用变量表示
3.组织测试数据,格式[(),(),()] 一个元组就是一组测试数据
4.参数化,在测试方法上方使用装饰器 @parameterized.expand(测试数据)
5.运行(直接 TestCase 或者使用 suite 运行)
- add_data.json 要使用的数据
[
[1,1,2],
[1,2,3],
[4,1,5],
[2,3,5],
[4,5,9]
]
- 读取json文件,并返回需要的数据
import json
def build_add_data():
with open('add_data.json') as f:
data = json.load(f) # [[],[],[]] ---> [(),(),()]
return data
- 使用数据
class TestAdd(unittest.TestCase):
@parameterized.expand(build_add_data())
def test_add(self,a,b,expect):
print(f'a:{a}:,b:{b},expect:{expect}')
self.assertEqual(expect, add(a, b))
- 复杂的数据格式处理
[
{
"a": 1,
"b": 2,
"expect": 3
},
...
]
def build_add_data_1():
with open('add_data_1.json') as f:
data_list = json.load(f) # [{},{},{}}] ---> [(),(),()]
new_list = []
for data in data_list:
# 简化写法,因为字典里所有值都是我们需要的,如果有某些值不需要,再使用get获取所需的键值
new_list.append(tuple(data.values()))
# a = data.get('a')
# b = data.get('b')
# expect = data.get('expect')
# new_list.append((a,b,expect))
return new_list
三、测试报告
HTML测试报告:执行完测试用例之后,以HTML方式将执行结果生成报告
- 安装
pip install HTMLTestReport - 使用
1.导包 unittest、HTMLTestReport
2.组装用例(套件、loader)
3.使用HTMLTestReport中的runner执行套件
4.查看报告
import unittest
from htmltestreport import HTMLTestReport
# 套件
from day0610.demo6 import TestAdd
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestAdd))
# 运行对象
# runner = HTMLTestReport(报告文件路径.html,报告标题,其他描述信息)
runner = HTMLTestReport('test_repoet.html','加法用例测试报告','描述信息')
runner.run(suite)
获取项目的绝对路径
- 项目是分目录书写的,使用相对路径,可能会出现找不到文件的情况,此时需要使用相对路径
- 方法:
1.在项目的根目录中,创建一个python文件(app.py)
2.在这个文件中,获取项目的目录,在其他代码中使用路径拼接完成绝对路径的书写
获取当前文件的绝对路径:abspath = os.path.abspath(_file_)
获取文件路径的目录名称:dirname = os.path.dirname(filepath)
import os
# __file__ 特殊的变量,表示当前代码文件名
path1 = os.path.abspath(__file__) # D:\PyCharm\Code\studyType\day0610\app.py
print(path1)
path2 = os.path.dirname(path1) # D:\PyCharm\Code\studyType\day0610
print(path2)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
登录案例
- 对登录函数进行测试,登录函数定义在tool.py中
- 在case中书写测试用例对login函数 进行测试,使用断言
- 将login函数的测试数据定义在json文件中,完成参数化(data目录)
- 生成测试报告,在report目录中
- 登录函数
def login(username,password):
if username == 'admin' and password == '123456':
return '登录成功'
else:
return '登录失败'
- 测试数据
# login_data.json
[
{
"desc": "正确的用户名和密码",
"username": "admin",
"password": "123456",
"expect": "登录成功"
},
{
"desc": "错误的用户名",
"username": "root",
"password": "123456",
"expect": "登录失败"
},
{
"desc": "错误的密码",
"username": "admin",
"password": "123",
"expect": "登录失败"
},
{
"desc": "错误的用户名和密码",
"username": "root",
"password": "123",
"expect": "登录失败"
}
]
- 读取数据的方法
# read_data.py
def build_login_data():
with open(BASE_DIR+'/data/login_data.json',encoding='utf-8') as f:
data_list = json.load(f)
new_list = []
for data in data_list:
# 字典中的desc不需要
username = data.get('username')
password = data.get('password')
expect = data.get('expect')
new_list.append((username,password,expect))
return new_list
- 测试用例代码
# test_login.py
import unittest
from day0610.commom.read_data import build_login_data
from day0610.tool import login
from parameterized import parameterized
class TestLogin(unittest.TestCase):
@parameterized.expand(build_login_data())
def test_login(self,username,password,expect):
print(f'username:{username},password:{password},expect:{expect}')
self.assertEqual(expect,login(username,password))
- Suite代码
# login.py
import unittest
from day0610.app import BASE_DIR
from day0610.case.test_login import TestLogin
from htmltestreport import HTMLTestReport
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestLogin))
runner = HTMLTestReport(BASE_DIR+'/report/login.html','登录的测试报告')
runner.run(suite)
跳过
对于一些未完成的或者不满足测试条件的测试函数和测试类,可以跳过执行
- 使用方法
直接将函数标记成跳过:@unittest.skip(‘跳过的原因’)
根据条件判断测试函数是否跳过:@unittest.skipif(condition,reason)
import unittest
version = 10
class TestSkip(unittest.TestCase):
@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')
if __name__ == '__main__':
unittest.main()