一、单元测试框架
1.什么是单元测试框架
单元测试框架是在自动化测试或者白盒测试中对软件的最小单元(函数、方法)进行测试的框架
2.单元测试框架分类
Python:unittest,pytest(主流)
Java:Testing(主流)和Junit
3.单元测试框架主要做什么
发现测试用例
执行测试用例
判断测试结果
生成测试报告
4.集成度对比
Pytest>UnitTest>RobotFramework>airtest
二、Pytest间接以及常用插件安装
1.pytest是一个非常成熟的单元测试框架,灵活和简单,安装命令:pip install pytest
2.可以结合selenium,requests,appium完成各种不同的自动化
3.可以生成自定义allure报告以及和Jenkins持续集成
4.pytest有很多强大的插件
pytest
pytest-html(生成html报告的插件)
pytest-xdist(多线程运行的插件)
pytest-ordering(改变用例的执行顺序的插件)
pytest-rerunfailures(失败用例重跑的插件)
allure-pytest(生成美观自定义的allure报告)
通过在项目的根目录下新建一个:requirements.txt文件保存插件,通过命令安装:pip install -r requirements.txt
三、pytest默认测试用例的规则以及基础应用
1.模块名必须以test_开头或者test结尾
2.测试类必须以Test开头,并且不能带有init方法
3.测试用例必须以test开头
4.断言使用基本的assert即可
import pytest
class TestCase:
def test_01(self):
print('第一条用例')
assert 1 == 1
def test_02(self):
print('第二条用例')
assert 2 == 3
执行:
1).通过命令行方法执行
# 执行所有用例
pytest
# 执行某个用例文件
pytest test_case.py
# 执行某用例中某方法
pytest test_case.py::test_01
执行的参数:
-v 输出详细信息,如:pytest -v
-s 输出调试信息
-rA 测试结果的简单统计
-n 多线程运行,如:pytest -vs -n=2
--reruns num 失败重跑(前提安装插件:pytest-rerunfailures)
raise Exception() 抛出异常
try except 解决异常
-x 出现一个用例失败则停止测试 ,如:pytest -vs -x
---maxfail 出现几个失败才终止,如:pytest -vs --maxfail=2
--html 生成html的报告(前提安装插件:pytest-html),如:pytest -vs --html ./report/report.html
-k 运行测试用例名称中包含某个字符串的测试用例, 如:pytest -vs -k "name"
2).通过主函数main()方法执行
if __name__ == '__main__':
pytest.main()
添加参数:pytest.main(["-vs"])
3).通过全局配置文件pytest.ini文件执行
注意:
a.一般放在项目的根目录,名称必须是pytest.ini,当有中文时可能需要改变编码格式为GB2312
b.pytest.ini文件可以改变默认的测试用例规则
不管是命令行运行还是主函数运行,都会加载这个配置文件
c.Pytest默认寻找当前路径下所有的文件与子文件中以test开头的文件夹、文件、函数作为识别对象
d.Pytest默认不输出任何打印信息,如果需要看打印信息,要在运行时添加-s指令,如:pytest.main(['-s'])
pytest.ini
[pytest]
addopts = -vs -m "smoke"
testpaths = ./testcases
python_files = test_*.py # 测试test_开头的文件
python_classes = Test* # 测试Test开头的类
python_functions = test_* # 测试test_开头的方法
# 测试用例标记分组执行
markers =
smoke:冒烟用例
product_manage:商品管理
这里的-m "smoke"表示只执行冒烟用例
# 标记分组执行
import pytest
class TestCase:
# 标记分组执行
@pytest.mark.smoke
def test_01(self):
print("测试pytest案例01")
def test_02(self):
print("测试pytest案例02")
四、pytest跳过测试用例
1.无条件跳过
@pytest.mark.skip(reason="无理由跳过")
2.有条件跳过
@pytest.mark.skipif(reason="工作经验少于10年跳过")
# pytest跳过测试用例
import pytest
class TestCase:
# 无条件跳过
@pytest.mark.skip(reason="无理由跳过")
# 有条件跳过
# @pytest.mark.skip(reason="工作经验不少于10年")
def test_01(self):
print("测试pytest案例01")
def test_02(self):
print("测试pytest案例02")
五、pytest测试用例的前后置固件
common_util.py
class CommonUtil:
def setup_class(self):
print("每个类用例之前执行一次")
def teardown_class(self):
print("每个类用例之后执行一次")
def setup(self):
print("每个用例之前执行一次")
def teardown(self):
print("每个用例之后执行一次")
test_case.py
import pytest
from common.common_util import CommonUtil
class TestCase(CommonUtil):
def test_01(self):
print("测试pytest案例01")
def test_02(self,):
print("测试pytest案例02")
六、使用fixture实现部分前后置
@pytest.fixture(scope="function",autouse=False,params=None,ids=None,name=None)
1.scope:作用域
function: 在函数之前和之后执行
1).手动调用的方式是在测试用例的参数里加入fixture的名称
# 使用fixture实现部分前后置
import pytest
@pytest.fixture(scope="function")
def exe_database_sql():
print("执行sql查询")
class TestCase():
@pytest.mark.skip(reason="无理由跳过")
def test_01(self):
print("测试pytest案例01")
# 手动执行方法fixture
def test_02(self, exe_database_sql):
print("测试pytest案例02")
2).如果说fixture通过return或yield返回值的话,可以把这个值传递到测试用例当中,并且值是通过固件的名字传递的
# 使用fixture实现部分前后置
import pytest
# 自动执行方法fixture
@pytest.fixture(scope="class", autouse=True)
def exe_database_sql():
print("执行sql查询")
yield "success"
print("关闭数据库链接")
class TestCase():
def test_01(self):
print("测试pytest案例01")
def test_02(self, exe_database_sql):
print("测试pytest案例02")
print(exe_database_sql)
class: 在类之前和之后执行
1).手动调用的方式是在类的上面加上@pytest.mark.usefixtures("name")装饰器调用
# 使用fixture实现部分前后置
import pytest
@pytest.fixture(scope="class", autouse=False)
def exe_database_sql():
print("执行sql查询")
yield "success"
print("关闭数据库链接")
# 手动执行类fixture
@pytest.mark.usefixtures("exe_database_sql")
class TestCase(CommonUtil):
def test_01(self):
print("测试pytest案例01")
def test_02(self):
print("测试pytest案例02")
print(exe_database_sql)
package/session: 在整个项目会话之前和之后执行
1).一般会结合conftest.py文件来实现
module:在模块级别中只执行一次
2.autouse: 自动执行,默认是Flase
如果希望在另外一个py文件中调用需要结合conftest.py文件使用
3.params:实现参数化
1.如何把值传到fixture是通过在fixture函数的参数里加入request来接受参数,然后通过request.param来取值(此处param没有s)
# param实现参数化
import pytest
# 使读取数据方法
def read_yaml():
return ['成龙', '甄子丹', '李连杰']
@pytest.fixture(scope="function", autouse=False, params=read_yaml())
def exe_database_sql(request):
print(request.param)
print("执行sql查询")
yield request.param
print("关闭数据库链接")
class TestCase:
def test_01(self):
print("测试pytest案例01")
def test_02(self, exe_database_sql):
print("测试pytest案例02")
print(exe_database_sql)
4.ids:不能单独使用,必须和params一起使用,作用是对参数起别名
# ids对参数起别名
import pytest
# 使读取数据方法
def read_yaml():
return ['成龙', '甄子丹', '李连杰']
@pytest.fixture(scope="function", autouse=False, params=read_yaml(), ids=['chenglong', 'zenzidan', 'lilianjie'])
def exe_database_sql(request):
print("执行sql查询")
yield request.param
print("关闭数据库链接")
class TestCase:
def test_01(self):
print("测试pytest案例01")
def test_02(self, exe_database_sql):
print("测试pytest案例02")
print(exe_database_sql)
5.name:作用是给fixture起别名
特别注意:一旦使用了别名,fixture名称就不能用,只能用别名name
# name对fixture起别名
import pytest
# 使读取数据方法
def read_yaml():
return ['成龙', '甄子丹', '李连杰']
@pytest.fixture(scope="function", autouse=False, params=read_yaml(), ids=['chenglong', 'zenzidan', 'lilianjie'], name="db")
def exe_database_sql(request):
print("执行sql查询")
yield request.param
print("关闭数据库链接")
class TestCase:
def test_01(self):
print("测试pytest案例01")
def test_02(self, db):
print("测试pytest案例02")
print(db)
用例顺序按照指定方式:@pytest.mark.run(order=)
指定用例运行:@pytest.mark.任意名字,执行时添加‘-m 任意名字’
import pytest
class TestCase:
@pytest.mark.smoke
def test_01(self):
print("测试pytest案例01")
@pytest.mark.run(order=1)
def test_02(self):
print('测试pytest案例02’)
if __name__ = '__main__':
pytest.main(['s', '-v', '-m', 'smoke'])
七、fixture结合conftest.py文件使用
1.conftest.py是专门用于存放fixture的配置文件,名称是固定的不能变
conftest.py
import pytest
# 使读取数据方法
def read_yaml():
return ['成龙', '甄子丹', '李连杰']
@pytest.fixture(scope="function", autouse=False, params=read_yaml(), ids=['chenglong', 'zenzidan', 'lilianjie'], name="db")
def exe_database_sql(request):
print("执行sql查询")
yield request.param
print("关闭数据库链接")
test_case.py
# 直接使用固件fixture的别名db
class TestCase:
def test_01(self):
print("测试pytest案例01")
def test_02(self, db):
print("测试pytest案例02")
print(db)
2.在conftest.py文件里所有的方法在使用时不需要打包导包
3.conftest.py文件可以有多个,并且多个conftest.py文件里面的多个fixture可以被一个用例调用
八、setup、teardown、setup_class、teardown_class、fixture、conftest优先级
会话:fixture的session级别优先级最高,setup_session
类:fixture的class级别优先级最高
setup_class > setup method > setup > teardown > teardown method > teardown class
类:setup_class
函数:fixture的function级别优先级最高,函数:setup_function
九、总结pytest执行过程
1.查询当前目录下的conftest.py文件
2.查询当前目录下的pytest.ini文件,找到测试用例的位置
3.查询用例目录下的conftest.py文件
4.查询测试用例的py文件中是否有setup、teardown、setup_class、teardown_class
5.再根据pytest.ini文件的测试用例的规则去查找用例并执行
十、pytest断言
就是使用pytest自己的断言assert
1.assert flag is True
2.assert 1 == 1
3.assert 'a' in 'abc'
# pytest断言assert
class TestCase:
def test_01(self):
print("测试pytest案例01")
assert 1 > 2
def test_02(self, db):
print("测试pytest案例02")
print(db)
assert 1 >= 1
十一、pytest结合allure-pytest插件生成美观的报告
1.安装allure-pytest插件
2.下载allure,下载解压之后,还需配置环境变量(把allure目录下的bin目录配置到系统变量的path路径),下载地址:https://github.com/allure-framework/allure2/releases
验证allure是否安装成功:allure --version
1).先在dos窗口验证
allure --version
2).在pycharm验证(如果验证失败,重启pycharm)
3.生成allure报告
1)生成临时的json报告,在pytest.ini文件加入以下内容:
addopts = -vs --alluredir=./temps --clean-alluredir
--alluredir=./temps 生成临时报告
--clean-alluredir 清空临时报告
2)生成正式的allure报告
run.py
import pytest
import os
import time
if __name__ == '__main__':
pytest.main()
time.sleep(3)
os.system("allure generate ./temps -o ../report --clean")
3)定制化
注意:安装的allure-pytest和allure版本需相匹配
十二、pytest之parametrize()实现数据驱动
方法:@pytest.mark.parametrize(args_name,args_value)
args_name:参数名称,用于将参数值传递给函数
args_vlaue:参数值(列表和字典列表,元祖和字典元祖)有n个值用例执行n次
第一种用法:
# pytest之parametrize()实现数据驱动
import pytest
class TestCase:
@pytest.mark.parametrize('caseinfo', ['chenglong', 'zenzidan', 'lilianjie'])
def test_01(self, caseinfo):
print("测试pytest案例01")
print(caseinfo)
第二种用法:
# pytest之parametrize()实现数据驱动
import pytest
class TestCase:
@pytest.mark.parametrize('arg1, arg2', [['chenglong', 'zenzidan'], ['lilianjie', 'xietingfeng']])
def test_01(self, arg1, arg2):
print("测试pytest案例01:"+str(arg1)+""+str(arg2))
十三、YAML格式测试用例读写,清楚封装
1.yaml是一种数据格式,扩展名可以使yaml,yml,支持#注释,通过缩进表示层级,区分大小写。
用途:
用于做配置文件(yam,ini)
用于编写自动化测试用例
2.数据组成
1)map对象,键值对(键和值之间有空格)
yaml_util.py
# 读取yaml,map对象
import yaml
import os
# 获取项目的根目录
def get_obj_path():
# 当前文件的目录
# return os.path.dirname(__file__)
# 项目根目录
return os.path.dirname(__file__).split('common')[0]
# 读取yaml,需要安装pip install pyyaml
def read_yaml(yamlpath):
# with open('yaml文件的路径', mode='r', encoding='utf-8') as f: value = yaml.load(stream=f, Loader=yaml.FullLoader)
with open(get_obj_path()+yamlpath, mode='r', encoding='utf-8') as f: value = yaml.load(stream=f, Loader=yaml.FullLoader)
return value
# # 读取yaml文件,r为读取文件内容
# files = open(filename, 'r', encoding='utf-8')
# # 读取files的内容
# data = yaml.load(files, Loader=yaml.FullLoader)
# return data
if __name__ == '__main__':
print(read_yaml('pytest1/get_token.yaml'))
get_token.yaml
name: 'token value'
2)数组(list)使用'-'表示列表
yaml_util1.py
# 读取yaml,list数组
import yaml
import os
# 获取项目的根目录
def get_obj_path():
# 当前文件的目录
# return os.path.dirname(__file__)
# 项目根目录
return os.path.dirname(__file__).split('common')[0]
# 读取yaml,需要安装pip install pyyaml
def read_yaml(yamlpath):
# with open('yaml文件的路径', mode='r', encoding='utf-8') as f: value = yaml.load(stream=f, Loader=yaml.FullLoader)
with open(get_obj_path()+yamlpath, mode='r', encoding='utf-8') as f: value = yaml.load(stream=f, Loader=yaml.FullLoader)
return value
if __name__ == '__main__':
print(read_yaml('pytest1/get_token1.yaml'))
get_token1.yaml
yamlist:
- name1: '成龙'
- name2:
- title1: '李连杰'
- title2: '谢霆锋'
- name3: '甄子丹'
十四、parametrize结合yaml实现接口自动化测试
# 未封装request
import requests
import pytest
from common.common_util import CommonUtil
from common.yaml_util import read_yaml
class TestApi(CommonUtil):
session = requests.session()
@pytest.mark.parametrize('caseinfo', read_yaml('./pytest1/get_token2.yaml'))
def test_01_get_token(self, caseinfo):
print('获取统一接口鉴权码')
print(caseinfo)
name = caseinfo['name']
method = caseinfo['request']['method']
url = caseinfo['request']['url']
data = caseinfo['request']['data']
validate = caseinfo['validate']
res = TestApi.session.request(method=method, url=url, params=data)
result = res.json()
print(result)
封装request
requests_util.py
import requests
class RequestUtil:
session = requests.session()
def send_request(self, method, url, data, **kwargs):
method = str(method).lower()
if method == "get":
res = RequestUtil.session.request(method, url, params=data, **kwargs)
elif method == "post":
res = RequestUtil.session.request(method, url, json=data, **kwargs)
return res
test_case.py
# 封装request
import pytest
from common.common_util import CommonUtil
from common.yaml_util import read_yaml
from common.requests_util import RequestUtil
class TestApi(CommonUtil):
@pytest.mark.parametrize('caseinfo', read_yaml('./pytest1/get_token2.yaml'))
def test_01_get_token(self, caseinfo):
print('获取统一接口鉴权码')
print(caseinfo)
name = caseinfo['name']
method = caseinfo['request']['method']
url = caseinfo['request']['url']
data = caseinfo['request']['data']
validate = caseinfo['validate']
res = RequestUtil.session.request(method=method, url=url, params=data)
result = res.json()
print(result)
十五、接口自动化测试框架的封装
1.yaml文件如何实现接口关联封装
2.yaml文件如何实现动态参数的处理
3.yaml文件如何实现文件上传
4.yaml文件如何实现解决断言,特别是当有参数化的时间如何断言
5.yaml文件数据量太大如何处理
6.接口自动化框架的扩展性:加密接口、签名接口、自定义的功能接口
接口自动化测试框架规则
1.必须有四个一级关键字:name,base_url,rquest,validate
2.在request一级关键字下必须包括两个二级关键字:method,url
3.传参方式:在request一级关键字下,通过二级关键字传递参数
1)如果是get请求,通过params传参
2)如果是post请求,传json格式,通过json关键字传参;传表单格式,通过data关键字传参
3)如果是文件格式,通过files关键字传参,如:
files: media: "D:\video.png"
4.如果需要做接口关联,必须使用一级关键字:extract,框架支持json提取和正则提取
提取:
1)json提取方式:
extract: access_token: access_token
2)正则表达式方式:
extract:access_token: '"access_token":"(.*?)"'
5.热加载,当yaml需要使用动态参数时,那么可以在debug_talk.py文件中写方法调用,如:
# 获取随机数的方法
def get_random_number(self, min, max):
return random.randint(int(min), int(max))
6.框架支持两个断言方式,分别为equals和contains断言,如:
validate:
- equals: {status_code: 205}
- contains: {access_token}
7.数据驱动使用csv和一级关键字parameters实现。
1)yaml写法
parameters:
name_appid: appidvalue
2)csv写法
8.日志监控,异常处理以及基础路径的设置
十六、YAMLJ简介
1.用途
1)用于做配置文件,配置全局的数据:环境变量、数据库信息、账号信息、日志格式、日志报告名称等。
2)用于写测试用例(接口自动化测试用例),用于数据驱动
2.YAML简介
yaml是一种数据格式,支持注释、换行、多行字符串、裸字符串等
3.yaml语法规则
1)大小写敏感
2)使用缩进表示层级关系
3)缩进不管空格的数量,只要层级的左边对齐就行
4)#表示注释
4.yaml和json数据结构对比
JSON:
1)Map对象:键值对(字典dict),使用{}括起来,如:
{name: 陈铭}
2)数组(列表list),使用[]括起来,如:
[{name: 陈铭}]
YAML:
1)Map对象:键值对(字典dict)
name: 陈铭
# 一行的写法
list: {name: 陈铭}
2)数组(列表list),用一组横行'-'开头
yamllist:
- name: 陈铭
# 一行的写法
yamlist: [{name: 陈铭}]
5.yaml配置文件处理