测试框架 : unittest pytest,技术栈,提供了表示测试用例,发现测试用例,执行测试用例。生成测试结果报告…
自动化测试框架:利用好技术栈
【python+loguru+openpyxl+pytest+allure+requests】+【代码封装思想
+数据分离思想+代码分层思想+数据驱动思想】
1、手工测试和自动化测试的思路流程: --任何语言都是一样的 通用的
手工执行测试:
熟悉业务-- 写用例(分模块)-- 执行用例并记录 -生成本轮的测试报告
自动化测试:
熟悉业务 – 写用例(手工用例转化为自动化测试用例)-- 用代码表达用例 (代码写出用例) – 代码收集测试用例-- 代码执行测试用例 – 代码生成测试报告。
自动化的思路基本是跟手工测试一样的,建立在手工测试基础上的一种更高效
率的进阶和升华。
2、自动化测试搭建有哪些框架
unittest : 内置库,有二次开发的库,比如unittestreport,不太灵活 不太智能。
pytest: 第三方库,现在用的更多。-- 主流 只能需要安装:pip install pytest
安装完可以直接 使用pytest 运行用例
pytest和unittest都是单元测试框架,可以用来编写测试用例,收集用例,运行用例,生成报告,实现前后置。
pytest编写测试用例 【2种方式】
1、pytest用例语法规则:
1)测试函数的形式写: 函数名要以test_开头
import random
# pytest用例
import pytest
@pytest.mark.p2
@pytest.mark.p1
def test_demo():
assert 1 == 2
def test_dv():
return 10/0
- 测试类的形式写: 类名要以Test开头,类里面的方法也要以test_开头 才会被识别为pytest的用例。
import random
# 普通函数
import pytest
def ger_ran():
num = random.randint(1,30)
return num
class TestDemo:
@pytest.mark.p1
def test_01(self):
"""第一条用例"""
assert ger_ran() > 15
@pytest.mark.p2
def test_02(self):
"""第二条用例"""
assert ger_ran() < 20
2、运行pytest用例:
1) 运行的单个模块用例,右键运行 点击三角符号运行
2) 完整项目框架里每个模块单独用一个py文件管理,需要收集所有模块的用例,一起执行。
pytest智能自动收集所有用例:pytest.main(): 自动在这个文件所在目录收集符合命名规则的文件
原理: 不同模块 不同目录的 主要符合命名规则的 都会拿过来执行。
-
rootdir 范围里找: main所在目录项目的根目录
-
文件名字: test_开头,或者 _test开头 【 但是一般推荐使用第一种,符合大家的习惯】
-
用例: 测试用例名字 test开头 | Test开头的类+ test_开头方法函名字
-
跟文件夹的名字 无关。可以放在文件夹里,且文件夹的名字无所谓。
"""
完整项目框架里每个模块单独用一个py文件管理,需要收集所有模块的用例,一起执行。
* pytest智能自动收集所有用例:pytest.main(): 自动在这个文件[run.py]所在目录收集符合命名规则的文件
* 原理: 不同模块 不同目录的 主要符合命名规则的 都会拿过来执行。
* - rootdir 范围里找: main所在目录项目的根目录
* - 文件名字: test_开头,或者 _test开头 【 但是一般推荐使用第一种,符合大家的习惯】
* - 用例: 测试用例名字 test开头 | Test开头的类+ test_开头方法函名字
* - 跟文件夹的名字 无关。可以放在文件夹里,且文件夹的名字无所谓。
问题:如果不想要收集所有的呢? 执行部分或者某些用例怎么实现?-- 测试用例过滤
* 方式一: 修改文件和用例方法的名字 --最简单最直观
* 方式二: 指定目录和文件执行 ,加参数控制
* 方式三: 加标签【类比手工测试用例的优先级: P1 P2 P3 P4 (important critical major) high medium low】, 加参数过滤用例
* 用例定义的加一个标签 : 用装饰器形式:@pytest.mark.p2
* 执行的时候 加参数 -m 标签
- 注意: 直接加标签 会有一个警告信息 但是不影响运行不报错; 但是不想出现这个警告 可以提前注册一下标签。
- 注册方法: pytest.ini配置文件在项目的根目录下面。 注册标签名。
[pytest]
markers=
p1
p2
p3
high
pytest.main([参数1,参数2,...])
allure报告失败了 原因2种:
1- 测试用例本身断言失败 : 预期结果 和执行结果不一样,可能是bug ,产品的缺陷;
2- 用例本身报错了: 代码设计问题
报告关注用例在执行用时: 面试题: 接口自动化总共覆盖了多少个用例?总共执行花了多少时间? == 【单个接口执行时间 * 总共用例数量】
- 单个接口用例执行 几十ms
- 总共用例数量: 手工用例 5000个,接口自动化测试覆盖率【回归测试】90%+ == 自动化用例数量:4500个
- 4500 * 200ms = 900000ms --> 900s ,15min
- 如果是业务流接口【多接口关联】 1s-10s
- UI自动化更慢: 页面加载速度有关。 1个用例几十s 。
完整的项目流程 集成储持续集成Jenkins 一起工作: 构建历史查看到每次构建的结果报错。-发送邮件。
os执行限制比较多,不推荐用。
"""
from tools.handle_path import log_path
# 存储日志文件代码
import pytest
from loguru import logger
from tools.handle_path import log_path
logger.add(sink=log_path,
encoding="UTF8",
level="INFO",
rotation="10MB",
retention= 20)
# 运行所有的用例:
# pytest.main([r"testcases\test_demo2_pytest.py"]) # 用例过滤 具体某个路径
# pytest.main([r"testcases\test_demo2_pytest.py","-m p1"]) # 用例过滤 标签过滤用例一个标签
# pytest.main(["-v",r"testcases\test_demo2_pytest.py","-m p1 or p2"]) # 用例过滤 标签过滤用例多个标签
pytest.main(["-v","--alluredir=outputs/allure_report","--clean-alluredir"])
# -v 参数详细信息显示
注意事项: 如果pytest没有识别出来,或者之前有运行过unittest框架用例,需要做如下配置: 【pycharm是自动检测 不需要额外的配置的。如果不行可以配置一下】
1、安装好之后,可以配置为pytest执行方式: file–setting–tools–Pythoniintegrated tools – Testing :修改为pytest; 如果用unittest 就选unitte
2、如果之前运行过unittest的,记得去右上角的运行按钮–edit configuration删除所有的运行记录。配置好了之后,就会自动识别为pytest的用例。-有个绿色的小按钮,可以运行和debug。
问题:如果不想要收集所有的呢? 执行部分或者某些用例怎么实现?
方式一: 修改文件和用例方法的名字
方式二: 指定目录和文件执行 ,加参数控制
方式三: 加标签【类比手工测试用例的优先级: P1 P2 P3 P4(important critical major) high medium low】, 加参数过滤用例用例定义的加一个标签 : 用装饰器形式:@pytest.mark.p2执行的时候 加参数 -m 标签
pytest生成测试报告 – allure 插件
这是一个开源的 独立的展示测试报告的工具,需要各种语言的框架去执行测试用例,生成allure可以
解析的结果文件,从而生成allure的测试报告。
官方文档: https://docs.qameta.io/allure-report/
跨平台的 python java go js等语言都支持,官方文档里有显示;
对于python来说,支持pytest,还有两个两个,不那么主流;unittest不支持
1)安装allure工具:
a、安装jdk
b、下载allure,配置环境变量
-
解压到任意目录,建议目录不要在C盘 不要太深;
-
进入allure解压后的目录,找到bin目录,然后把bin目录配置为path 的环境变量
- path添加如下目录即可:如 D:\allure\allure-2.23.1\bin
-
cmd里检查环境变量是否配置成功: 输入allure --version 看是否出现版本。
c、pytest要生成allure解析的测试结果文件,安装pytest的allure插件
【Python的第三方库】: pip install allure-pytest==2.11.1
或者pycharm安装;
-
运行用例的时候就会自动生成结果文件: 使用pytest运行的参数:加一个参数:–alluredir=相对于rootdir的目录
-
在当前目录下就会生成一个reports目录下的allure目录,然后结果文件都生成在里面。-json文件给allure看的
-
这些只是结果文件,还没有生成报告,接下来要allure来解析这些结果文件 生成allure的报告 --HTML页面给人看的
-
使用allure命令生成报告:
- cmd里或者terminal里,跳转到rootdir目录;
- 运行命令: allure serve reports_allure === serve后面跟的是自己定义的结果文件的目录,相对于rootdir的目录;
import pytest
#加一个参数:--alluredir=相对于rootdir的目录
pytest.main(["--alluredir=reports_allure"])
# 每次执行一次就会又重复生成结果文件不会清楚;会影响报告结果,所以
需要加一个清除的参数:
pytest.main(["-v","--alluredir=reports/allure",
"--clean-alluredir"]) # 清除上次执行的结果文件
pytest的数据驱动: data deriven test,是一种设计思想;
在什么场景下可以使用这种思想:
1)有一个通用的流程:不同的测试数据使用的同一个方法执行;
2) 多组数据,都走这个流程;不同的数据使用同一个方法 驱动不同的结果。
ddt的语法:
- @pytest.mark.parametrize 是个装饰器,里面两个数据: data 和datas
意思就是: 将datas里每个成员传递给data这个变量接受; - data注意要加引号,虽然是个变量 但是要加引号
- 后面的用例里的参数data 都是必须要要跟这个装饰器里的data保持一致。
- 运行结果:
- 会运行3条测试用例: 几条数据就运行几条测试用例
- 然后就算前面的断言失败了,也依然会允许后续的用例。
第一种写法:函数形式的用例:
@pytest.mark.parametrize("变量名",装用例的列表/字典/元组)
def test_login(变量名): # 注意,变量名跟装饰器里的变量名保持一致
res = login(变量名["name"],变量名["pwd"])
assert res == 变量名["expect"]
范例:
import pytest
from loguru import logger
# login流程
def login(username,password):
if username != "admin":
return "用户名错误"
if password != "123456":
return "密码错误"
return "登录成功"
# 准备测试用例数据 - 正常 异常数据
datas = [{"name":"admin","pwd":"123456","expected":"登录成功1"},
{"name":"lemon","pwd":"123456","expected":"用户名错误"},
{"name":"admin","pwd":"1234","expected":"密码错误"}]
# 数据驱动实现
@pytest.mark.parametrize("data",datas) # 依次取到datas里面的每一个元素,传递给data接受
def test_login(data): # 把data变量传递给函数的参数 :data就是每一条用例的字典。
username = data["name"] # data : {"name":"admin","pwd":"123456","expected":"登录成功1"}
password = data["pwd"]
result = login(username,password)
logger.info("用例执行开始..")
# 断言
assert result == data["expected"]
第二种: 类形式的测试用例:–写在类上面也可以,就会给类下面每个方法都
进行数据驱动。
class TestDemoC:
@pytest.mark.parametrize("变量名",装用例的列表/字典/元组)
def test_login(变量名): # 注意,变量名跟装饰器里的变量名保持一致
res = login(变量名["name"],变量名["pwd"])
assert res == 变量名["expect"]
或者
@pytest.mark.parametrize("变量名",装用例的列表/字典/元组)
class TestDemoC:
def test_login1(变量名): # 注意,变量名跟装饰器里的变量名保持一致
res = login(变量名["name"],变量名["pwd"])
assert res == 变量名["expect"]
def test_login2(变量名): # 注意,变量名跟装饰器里的变量名保持一致
res = login(变量名["name"],变量名["pwd"])
assert res == 变量名["expect"]
范例:
import pytest
from loguru import logger
# login流程
def login(username,password):
if username != "admin":
return "用户名错误"
if password != "123456":
return "密码错误"
return "登录成功"
# 准备测试用例数据 - 正常 异常数据
datas = [{"name":"admin","pwd":"123456","expected":"登录成功1"},
{"name":"lemon","pwd":"123456","expected":"用户名错误"},
{"name":"admin","pwd":"1234","expected":"密码错误"}]
# 数据驱动实现 --类形式
# class TestDDT:
# @pytest.mark.parametrize("data",datas) # 依次取到datas里面的每一个元素,传递给data接受
# def test_login(self,data): # 把data变量传递给函数的参数 :data就是每一条用例的字典。
# username = data["name"] # data : {"name":"admin","pwd":"123456","expected":"登录成功1"}
# password = data["pwd"]
# result = login(username,password)
# logger.info("用例执行开始..")
# # 断言
# assert result == data["expected"]
@pytest.mark.parametrize("data", datas) # 依次取到datas里面的每一个元素,传递给data接受
class TestDDT:
def test_login(self,data): # 把data变量传递给函数的参数 :data就是每一条用例的字典。
username = data["name"] # data : {"name":"admin","pwd":"123456","expected":"登录成功1"}
password = data["pwd"]
result = login(username,password)
logger.info("用例执行开始..")
# 断言
assert result == data["expected"]
def test_login2(self,data): # 把data变量传递给函数的参数 :data就是每一条用例的字典。
username = data["name"] # data : {"name":"admin","pwd":"123456","expected":"登录成功1"}
password = data["pwd"]
result = login(username,password)
logger.info("用例执行开始..")
# 断言
assert result == data["expected"]
pytest的前置后置-夹具 fixture
- 有些内容是在每个用例我执行之前都要运行操作:
- 接口: 购物车模块先登录 --登录结果
UI: 每次用例 打开浏览器 --driver
- 接口: 购物车模块先登录 --登录结果
- 有些内容再每个用例之后都要运行操作:
- 接口: 数据清除
- UI:关闭浏览器
叫做用例的前置 和后置。 pytest测试框架 统一叫做夹具。fixture。
- 定义夹具语法:
在函数前面加一个装饰器: @pytest.fixture, 申明后就是夹具。
@pytest.fixture # 申明它是一个fixture的函数
def setup_teardown():
print("我是前置代码..")
print("我是后置代码..")
在测试用例里调用前置后置函数:
def test_demo(setup_teardown):
assert 1 == 1
yield: 前置和后置的划分 ,还可以设置夹具的返回值
import pytest
@pytest.fixture # 申明它是一个fixture的函数
def setup_teardown():
print("我是前置代码..")
yield # 作为前置和后置的划分线,后面还可以定义夹具的返回值。
print("我是后置代码..")
夹具的conftest 共享:
- 如果要直接实现多个模块共享夹具,可以用conftest。
- 1、创建一个conftest.py文件,把夹具代码复制进去
- 注意文件名不能改,只能是这个名字 conftest
- 2、调用conftest里的夹具,不需要导入conftest.py模块,可以自动查找fixture。
- 另外一个Python文件的测试用例直接调用这个夹具,不需要导入,直接调用可以运行
- 3、conftest.py的作用范围: conftest.py所在文件夹下面的所有用例。一般放在根目录下 或者testcases目录下都可以。
- 也有可能在同一个项目中不同的目录下创建conftest.py文件 这也是可以的;那么也是在它目录下生效;
- 超出了范围就会报错:E fixture ‘class_setup_teardown’ not found
conftest.py内容如下
- 1、创建一个conftest.py文件,把夹具代码复制进去
"""
夹具的共享: 夹具写在conftest文件里, 名字固定。 在这个范围内容的用例都可以自动发现里面夹具。【不需要导入】
- 在conftest的所在目录下的所有文件都可以发现他。 == 一般会放在跟目录 + 测试用例目录
- 如果超过范围: fixture 'setup_teardown' not found。
问题: 自己的模块有夹具,conftest也有 ,多个conftest在本目录和上级目录,优先用哪个?
- 大原则: 就近原则。
- 名字不一样,还以名字区分。
- 名字一样: 就近原则 ,优先用自己的夹具。
- 1) 如果用例所在的测试文件中 找fixture,找到了就用
- 2)如果1)中没有找到,就去当前文件所在目录下contest.py找,找到了就用
- 3)如果2)中没有找到,就去【当前文件所在目录的上一级目录】下contest.py找,找到了就用
- 4)一直摘到rootdir下面截止,没有找到 就报错。
夹具作用域类型:
- 函数级别:function: 函数前后执行 ==默认的
- 类级别的: class : 类前后执行 不会每个方法都执行一次
- 调用家眷写在第一个方法里。 第二个会有问题。
- module级别:
- session:会话级别
"""
import pytest
@pytest.fixture(scope="class") # 一定要申明夹具
def setup_teardown():
print("我是前置代码...")
yield "token_value:asjkhdaksjhda","user_id:11032"
print("我是后置代码...")
当用例的模块也有夹具和conftest优先会用哪个夹具?–就近原则
- 名字不一样,按照名字区分
- 名字一样,就近原则,优先用自己的夹具。
- 1)如果用例所在的测试文件中 找fixture,找到了就用
- 2)如果1)中没有找到,就去当前文件所在目录下contest.py找,找到了就用
- 3)如果2)中没有找到,就去【当前文件所在目录的上一级目录】下contest.py找,找到了就用
- 4)一直摘到rootdir下面截止,没有找到 就报错。
3、pytest的特色总结:
- 1) 可以直接使用assert断言,并且自动识别预期和实际结果,报错信息详细显示 : == 这里区别于unittest 的assertEqual 的断言方法
- 2)可以自动发现测试模块和测试函数里的测试用例,并自动收集执行;
- 3)有非常丰富的第三方插件,allure报告
- 4)pytest的数据驱动灵活
-
- pytest的夹具非常灵活且功能丰富
- conftest自动发现,不用导入共享
- 可以设置夹具返回值
- 前置后置写在一个夹具里
- 6)pytest还有筛选的功能:过滤某些用例执行和不执行-标签功能 【结合手工测试用例优先级 】
冒烟测试: P1
回归测试: P1 P2