1.接口自动化封装
接口关键字封装是指将接口测试过程中常用的操作和验证封装成可复用的关键字(或称为函数、方法),以提高测试代码的可维护性和可复用性
以下是一些常见的接口关键字封装方式:
-
发送请求:封装一个函数,接受参数如请求方法、URL、请求头、请求体等,使用相应的库发送请求,如requests库或HttpClient库。
-
响应验证:封装一个函数,接受参数如响应对象、预期结果等,通过断言或其他方式验证响应的状态码、响应体等是否符合预期。
-
数据提取:封装一个函数,接受参数如响应体、提取表达式等,使用正则表达式、XPath、JSONPath等方式提取需要的数据,并返回提取结果。
-
数据保存:封装一个函数,接受参数如文件路径、数据等,将数据保存到指定的文件中,如Excel、CSV、数据库等。
-
参数化配置:封装一个函数,接受参数如环境配置、数据文件路径等,根据不同的环境或数据文件读取对应的配置信息,如接口URL、认证信息等。
接口关键字代码案例如下
python
import allure
import requests
import jsonpath
import json
# 关键字驱动/基类/工具类
# 1. 发送请求:8种:post、get、put、delete...
# 2. 提取数据
# 补充知识点:方法的缺省值:params=None (默认值),如果没有传参则默认为None
class ApiKey:
@allure.step(">>>>>>:开始发送Get请求")
def get(self, url, params=None, **kwargs):
"""
发送get请求
:param url:接口请求url
:param params: 拼接在url的参数
:param kwargs: 其它的参数
:return: 返回请求后的数据
"""
# print(">>>>>>:开始发送Get请求")
return requests.get(url=url, params=params, **kwargs)
@allure.step(">>>>>>:开始发送Post请求")
def post(self, url, data=None, json=None, **kwargs):
"""
发送post请求
:param url: 接口请求url
:param data: data的请求数据
:param json: json的请求数据
:param kwargs: 其它的参数
:return: 返回请求后的数据
"""
# print(">>>>>>:开始发送Post请求")
res = requests.post(url=url, data=data, json=json, **kwargs)
print(">>>>>>:响应数据为:", res.json())
return res
@allure.step(">>>>>>:开始提取JsonPath数据")
def get_text(self, response, key):
"""
提取json当中的某个值
:param response: 需要提取的json数据,比如:{"msg":"登录成功"}
:param key: 对应要提取的jsonpath,比如: $.msg
:return: 返回提取数据之后的【第一个值】
"""
if isinstance(response,str):
# 是字符串,我就让它转一下类型
response = json.loads(response)
print(">>>>>>:开始提取JsonPath数据")
value_list = jsonpath.jsonpath(response, key)
print(">>>>>>:提取数据为:", value_list[0])
return value_list[0]
# 函数的入口:main
if __name__ == '__main__':
# 1. 实例化对象:ApiKey
ak = ApiKey()
# 2. 通过对应的类调用对应的方法 --四要素
# url = "http://shop-xo.hctestedu.com/index.php?s=/api/user/login"
# pulic_data = {"application": "app", "application_client_type": "weixin"}
# # 请求参数-- body (你的body数据是要以json进行提交,参数:json)
# data = {"accounts": "hami", "pwd": "123456", "type": "username"}
#
# res = ak.post(url=url, params=pulic_data, data=data)
# # 3. 提取数据
# text = ak.get_text(res.json(), "$.msg")
# 案例2 :"{"msg":"登录成功"}"
res ='{"msg":"登录成功"}'
text = ak.get_text(res, "$.msg")
2.测试用例的封装
python
# -*- coding: utf-8 -*-
# @Time : 2023/11/5 21:02
# @Author : Hami
import allure
# 导入:从项目根目录的下一级开始写
from encapsulation.P02_PytestFrame.api_keyword.api_key import ApiKey
# 方法要以:test 【默认的规则】
@allure.title("登录测试用例")
def test_loging():
# 1. 实例化对象:ApiKey
ak = ApiKey()
# 2. 通过对应的类调用对应的方法 --四要素
url = "http://shop-xo.hctestedu.com/index.php?s=/api/user/login"
pulic_data = {"application": "app", "application_client_type": "weixin"}
data = {"accounts": "hami", "pwd": "123456", "type": "username"}
# 3. 发送请求
res = ak.post(url=url, params=pulic_data, data=data)
# 4. 提取数据
text = ak.get_text(res.json(), "$.msg")
# 5. 进行断言
assert text == "登录成功", "期望结果和实际结果不一致"
# 扩展:
# 【不建议】
# 如果你在当前的文件去进行调试的话,需要把运行模式调整成:unittest
# 为什么要调整:因为我们pytest的一些运行规则unittest不支持
# 【建议的使用方式】
# 但是,我们用的是pytest ,它支持的是通过文件的入口去调试
# test_loging()
3.发送报告
python
import os
# 框架的运行入口
import pytest
if __name__ == '__main__':
# 会显示详细信息(-v) 表示在控制台输出内容(-s)
# pytest.main(["-vs"])
# 指定对应的文件夹名:路径[以当前运行文件的路径]开始找: ./ (当前目录)
# pytest.main(["-vs", "./testcase/test_DS_022_01.py"])
# 通过pytest 运行 并且生成allure报告【默认】
# 第一步: 指定运行文件,通过文件会生成运行结果后的数据放在:./result ; --clean-alluredir(每次运行之前清空这个文件夹的历史数据)
pytest.main(["-v", "./testcase/test_DS_033_03.py", "--alluredir", "./result", "--clean-alluredir"])
# 第二步:把数据转成测试报告(html):allure generate ./result -o ./report_allure --clean
# os.system() # 在cmd(终端)去运行命令
os.system("allure generate ./result -o ./report_allure --clean")
4.Allure报告进行优化之测试步骤
通过添加对应的测试步骤:
@allure.step("步骤内容注释")
@allure.title("测试用例标题")
python
# -*- coding: utf-8 -*-
# @Time : 2023/11/5 21:02
# @Author : Hami
import allure
# 导入:从项目根目录的下一级开始写
from encapsulation.P02_PytestFrame.api_keyword.api_key import ApiKey
# 方法要以:test 【默认的规则】
@allure.title("登录测试用例")
def test_loging():
# 1. 实例化对象:ApiKey
ak = ApiKey()
# 2. 通过对应的类调用对应的方法 --四要素
url = "http://shop-xo.hctestedu.com/index.php?s=/api/user/login"
pulic_data = {"application": "app", "application_client_type": "weixin"}
data = {"accounts": "hami", "pwd": "123456", "type": "username"}
# 3. 发送请求
res = ak.post(url=url, params=pulic_data, data=data)
# 4. 提取数据
text = ak.get_text(res.json(), "$.msg")
# 5. 进行断言
assert text == "登录成功", "期望结果和实际结果不一致"
# 扩展:
# 【不建议】
# 如果你在当前的文件去进行调试的话,需要把运行模式调整成:unittest
# 为什么要调整:因为我们pytest的一些运行规则unittest不支持
# 【建议的使用方式】
# 但是,我们用的是pytest ,它支持的是通过文件的入口去调试
# test_loging()
5.Allure报告进行优化之接口关联
python
# -*- coding: utf-8 -*-
# @Time : 2023/11/5 21:02
# @Author : Hami
import allure
# 导入:从项目根目录的下一级开始写
from encapsulation.P02_PytestFrame.api_keyword.api_key import ApiKey
# 加入购物车
# 方法要以:test 【默认的规则】
@allure.title("登录测试成功并且加入购物车")
def test_addcard():
with allure.step("第一步:登录接口的调用"):
# 接口一: 登录操作
# 1. 实例化对象:ApiKey
ak = ApiKey()
# 2. 通过对应的类调用对应的方法 --四要素
url = "http://shop-xo.hctestedu.com/index.php?s=/api/user/login"
pulic_data = {"application": "app", "application_client_type": "weixin"}
data = {"accounts": "hami", "pwd": "123456", "type": "username"}
# 3. 发送请求
res = ak.post(url=url, params=pulic_data, data=data)
# 4. 提取数据
text = ak.get_text(res.json(), "$.msg")
token = ak.get_text(res.json(), "$..token")
# 5. 进行断言
assert text == "登录成功", "期望结果和实际结果不一致"
with allure.step("第二步:加入购物车接口调用"):
# 接口二: 加入购物车
url = "http://shop-xo.hctestedu.com/index.php?s=/api/cart/save"
public_data = {"application": "app", "application_client_type": "weixin", "token": token}
data = {
"goods_id": "11",
"spec": [
{
"type": "尺寸",
"value": "M"
}
],
"stock": "10"
}
res = ak.post(url=url, params=public_data, data=data)
# ----------------------------------------
# 获取响应数据
result = res.json()
print(f"响应结果是:{result}")
# 期望结果
desire_res = "加入成功"
# 实际结果
reality_res = ak.get_text(result, "$.msg")
assert desire_res == reality_res, "期望结果和实际结果不一致"
6.接口关联改进
通过全局变量进行全局变量
我们可以把对应的api类和token设置为全局变量,这样就不用每一次请求都去获取token
python
# -*- coding: utf-8 -*-
# @Time : 2023/11/5 21:02
# @Author : Hami
import allure
# 导入:从项目根目录的下一级开始写
from encapsulation.P02_PytestFrame.api_keyword.api_key import ApiKey
# 加入购物车
# 方法要以:test 【默认的规则】
@allure.title("登录测试成功")
def test_login():
global ak, token
with allure.step("第一步:登录接口的调用"):
# 接口一: 登录操作
# 1. 实例化对象:ApiKey
ak = ApiKey()
# 2. 通过对应的类调用对应的方法 --四要素
url = "http://shop-xo.hctestedu.com/index.php?s=/api/user/login"
pulic_data = {"application": "app", "application_client_type": "weixin"}
data = {"accounts": "hami", "pwd": "123456", "type": "username"}
# 3. 发送请求
res = ak.post(url=url, params=pulic_data, data=data)
# 4. 提取数据
text = ak.get_text(res.json(), "$.msg")
token = ak.get_text(res.json(), "$..token")
# 5. 进行断言
assert text == "登录成功", "期望结果和实际结果不一致"
@allure.title("加入购物车")
def test_addcard():
with allure.step("第二步:加入购物车接口调用"):
# 接口二: 加入购物车
url = "http://shop-xo.hctestedu.com/index.php?s=/api/cart/save"
public_data = {"application": "app", "application_client_type": "weixin", "token": token}
data = {
"goods_id": "11",
"spec": [
{
"type": "尺寸",
"value": "M"
}
],
"stock": "10"
}
res = ak.post(url=url, params=public_data, data=data)
# ----------------------------------------
# 获取响应数据
result = res.json()
print(f"响应结果是:{result}")
# 期望结果
desire_res = "加入成功"
# 实际结果
reality_res = ak.get_text(result, "$.msg")
assert desire_res == reality_res, "期望结果和实际结果不一致"
7.fixture+conftest实现项目级的token多文件关联
在软件测试中,Fixture 是一种用于管理测试环境和测试数据的机制。它允许在测试函数或方法运行之前和之后执行特定的代码,以确保测试的可重复性和一致性。Fixture 主要用于设置测试环境、准备测试数据、执行清理操作等,以便测试能够按预期运行
语法结构
@pytest.fixture() 装饰器在 pytest 测试框架中用于定义测试夹具(fixture),并可以指定夹具的作用域。pytest 提供了四种作用域选项:
-
function (默认):夹具的作用范围限于单个测试函数。每个测试函数执行前都会调用该夹具。
-
class :夹具的作用范围限于测试类中的所有测试方法。每个测试类执行前都会调用该夹具。
-
module :夹具的作用范围限于单个测试模块。每个测试模块执行前都会调用该夹具。
-
session :夹具的作用范围限于整个测试会话。在整个测试会话期间只会调用一次该夹具
python
import pytest
import logging
from encapsulation.P02_PytestFrame.api_keyword.api_key import ApiKey
# 注意:名字是固定的。
# 可以写具体的方法、正常的调用某个接口
# pytest在运行的时候【会自动】先调起这个文件,后面还具体调用这个方法
# 方法:获取token
# 1. 正常的请求对应的接口并且提取数据
# 2. 告诉别人这个是一个测试夹具(测试前置、后置操作):@pytest.fixture()
from encapsulation.P02_PytestFrame.config import *
@pytest.fixture(scope= "session")
def token_fix():
"""
代表我们这个夹具运行一次
:return:
"""
print("开始运行:token_fix")
print("USERNAME:",USERNAME)
# 1. 实例化对象:ApiKey
ak = ApiKey()
# 2. 通过对应的类调用对应的方法 --四要素
# url = "http://shop-xo.hctestedu.com/index.php?s=/api/user/login"
url = PROJECT_URL+"?s=/api/user/login"
pulic_data = PULIC_DATA
data = {"accounts": USERNAME, "pwd": PASSWORD, "type": LOGINTYPE}
# 3. 发送请求
res = ak.post(url=url, params=pulic_data, data=data)
# 4. 提取数据
token = ak.get_text(res.json(), "$..token")
# 5. 返回数据
return ak,token
# 当执行一个case的时候会自动的调用这个方法:把对应的数据传过来给到call
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport(item, call):
# 通过 out = yield 定义了一个生成器。在生成器中,res = out.get_result() 获取了测试结果对象。
out = yield
res = out.get_result()
# res.when == "call":表示正在运行调用测试函数的阶段。
if res.when == "call":
logging.info(f"用例ID:{res.nodeid}")
logging.info(f"测试结果:{res.outcome}")
logging.info(f"故障表示:{res.longrepr}")
logging.info(f"异常:{call.excinfo}")
logging.info(f"用例耗时:{res.duration}")
logging.info("**************************************")
测试用例如下
python
# 导入:从项目根目录的下一级开始写
from encapsulation.P02_PytestFrame.api_keyword.api_key import ApiKey
# 加入购物车
# 方法要以:test 【默认的规则】
@allure.title("加入购物车")
def test_addcard(token_fix):
ak, token = token_fix
with allure.step("第二步:加入购物车接口调用"):
# 接口二: 加入购物车
url = "http://shop-xo.hctestedu.com/index.php?s=/api/cart/save"
public_data = {"application": "app", "application_client_type": "weixin", "token": token}
data = {
"goods_id": "11",
"spec": [
{
"type": "尺寸",
"value": "M"
}
],
"stock": "10"
}
res = ak.post(url=url, params=public_data, data=data)
# ----------------------------------------
# 获取响应数据
result = res.json()
print(f"响应结果是:{result}")
# 期望结果
desire_res = "加入成功31321312321"
# 实际结果
reality_res = ak.get_text(result, "$.msg")
assert desire_res == reality_res, "期望结果和实际结果不一致"
8.全局常量定义
把常用的服务IP、端口(环境变量)、公共参数等,统一定义到全局常量里,方便维护修改
-
新建一个py文件,专门用来存放常量,一般常量的命名都是大写;
-
对应的文件导入这个模块,即可使用
创建一个confic文件夹
python
# 配置常量的文件
# 环境变量
# 正式环境
PROJECT_URL = "http://shop-xo.hctestedu.com/index.php"
# 测试环境
# PROJECT_URL = "http://127.0.0.1:8080"
# 测试账号
USERNAME = "hami"
PASSWORD = "123456"
LOGINTYPE = "username"
PULIC_DATA = {"application": "app", "application_client_type": "weixin"}
2.logging
使用标准库提供的 logging API 最主要的好处是,所有的 Python 模块都可能参与日志输出,包括你自己的日志消息和第三方模块的日志消息
可以很方便的了解程序的运行情况
可以分析用户的操作行为、喜好等信息
方便开发人员检查bug(Allure就可以支持)
方便和运维系统对接,进行告警检测(必须输出日志文件,Allure无法解决,得用Logging)
日志等级从低到高的顺序是: DEBUG < INFO < WARNING < ERROR < CRITICAL
Pytest框架中的Logging应用
pytest要使用logging,必须在pytest.ini文件中将log_cli设置为true,否则不会有日志信息
pytest.ini日志如下
[pytest]
log_cli=true ; # 开启日志
log_level=NOTSET ; # 日志等级,默认是DEBUG
log_format = %(asctime)s %(levelname)s %(message)s # 日志日期
log_date_format = %Y-%m-%d %H:%M:%S # 日志时间,默认只有时间
log_file = ./log.log # 日志存放地方
log_file_level = info # 记录日志等级
log_file_format = %(asctime)s %(levelname)s %(message)s # 同记录时间一样
log_file_date_format = %Y-%m-%d %H:%M:%S # 日志时间,默认只有时间
pytest]
log_cli=true
log_level=NOTSET
log_format = %(asctime)s %(levelname)s %(message)s %(filename)s %(funcName)s %(lineno)d
log_date_format = %Y-%m-%d %H:%M:%S
log_file = ./log.log
log_file_level = info
log_file_format = %(asctime)s %(levelname)s %(message)s %(filename)s %(funcName)s %
(lineno)d
log_file_date_format = %Y-%m-%d %H:%M:%S
我们在pytest.ini中生成如上的日志,
打印故障信息和用例执行结果
我们结合conftest对测试用例进行结果的打印
python
# 当执行一个case的时候会自动的调用这个方法:把对应的数据传过来给到call
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport(item, call):
# 通过 out = yield 定义了一个生成器。在生成器中,res = out.get_result() 获取了测试结果对象。
out = yield
res = out.get_result()
# res.when == "call":表示正在运行调用测试函数的阶段。
if res.when == "call":
logging.info(f"用例ID:{res.nodeid}")
logging.info(f"测试结果:{res.outcome}")
logging.info(f"故障表示:{res.longrepr}")
logging.info(f"异常:{call.excinfo}")
logging.info(f"用例耗时:{res.duration}")
logging.info("**************************************")