目录
接口自动化框架:pytest + yaml + pymysql + requests + allure + logging
接口自动化框架中,对于每个框架/库 概念及使用 简单总结在了另一篇:http://t.csdnimg.cn/TjFLg
一、场景分析
对于一个自动化框架的搭建,首先我们得清楚该框架要实现什么?
现在有一个场景:请求某系统的登录页面,自动输入账号密码,并点击登录进入首页 对于这样一个场景,我想要实现的有以下几点:
目录【实现方法】:
- 脚本 scripts --- 测试登陆页面 【requests】
- 日志 log --- 程序运行日志【logging】
- 测试报告 report --- 【pytest 插件/allure】
实现自动化的一个核心思想就是,数据分离。也就是测试数据以参数的形式传递给测试脚本,而不是直接写道测试脚本中。
那么,我们就还需要一个 data目录 --- 【yaml】存放测试数据,于是,问题又来了,怎么读这个数据呢?所以需要封装一个使测试脚本读取数据的类,既然把它作为工具使用,把它称为工具类。所以,现在还需要一个 utils目录 来存放工具类。
二、具体分析
1.工具类utils
仔细分析一下,我们还有哪些需要的工具类:
-
文件路径:传递参数的时候直接写文件名就好,不用自己找路径
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# 定义yaml_file方法
def yaml_file(yaml_name):
yaml_dir = os.path.join(BASE_DIR, "data")
yaml_f = os.path.join(yaml_dir, yaml_name)
return yaml_f
# 封装日志路径
LOGER_DIR = os.path.join(BASE_DIR, "logs")
INFO_FILE = os.path.join(LOGER_DIR, "info_logs")
ERROR_FILE = os.path.join(LOGER_DIR, "error_logs")
# 封装allure路径
REPORT_DIR = os.path.join(BASE_DIR, "report")
REPORT_JSON = os.path.join(REPORT_DIR, "allure_json")
REPORT_HTML = os.path.join(REPORT_DIR, "allure_html")
print(REPORT_JSON)
print(REPORT_HTML)
# 封装case路径
CASE_FILE = os.path.join(BASE_DIR, "testcase")
print(CASE_FILE)
- 读取yaml文件:从指定的YAML文件中读取数据,并返回给测试脚本指定节点名称下的数据值列表。
import yaml
class YamlUtils:
@staticmethod
def read_yaml(yaml_path, node_name):
with open(yaml_path, 'r', encoding='utf-8') as file:
datas = yaml.safe_load(file)
login_dict = datas[node_name]
print(login_dict)
return list(login_dict.values())
- 日志记录器:生成日志需要导入python的标准日志模块实现
# 日志:记录正确/错误的接口信息
import logging
from utils.path_utils import INFO_FILE, ERROR_FILE
def get_logger():
# 初始化日志记录器,设置日志记录器的级别
logger = logging.getLogger()
logger.setLevel("DEBUG")
# 创建流处理器
ls = logging.StreamHandler()
ls.setLevel("INFO")
# 创建并配置文件处理器,用于记录INFO级别的日志(文件名,追加模式,编码格式)
lf_info = logging.FileHandler(filename=INFO_FILE, mode='a', encoding="utf-8")
lf_info.setLevel('INFO')
# 创建并配置文件处理器,用于记录ERROR级别的日志(文件名,追加模式,编码格式)
lf_er = logging.FileHandler(filename=ERROR_FILE, mode="a", encoding="utf-8")
lf_er.setLevel("ERROR")
# 创建并配置文件处理器,用于记录ERROR级别的日志(文件名,追加模式,编码格式)
fmat = "%(asctime)s - [%(filename)s - %(lineno)d] - %(levelname)s : %(message)s"
my_fmat = logging.Formatter(fmat)
# 给处理器添加格式器
logger.addHandler(ls)
logger.addHandler(lf_info)
logger.addHandler(lf_er)
# 给日志器添加处理器
ls.setFormatter(my_fmat)
lf_er.setFormatter(my_fmat)
lf_info.setFormatter(my_fmat)
return logger
logger = get_logger()
2.测试数据data
注意yaml的格式规范就好啦~
case_login: case_001: # 测试用户正常登陆场景 username: "admin" password: "123456" code: "6" exp: "操作成功" case_002: # 测试用户不存在的场景 username: "admin1" password: "123456" code: "6" exp: "登录用户:admin1 不存在" case_003: # 测试用户密码错误的场景 username: "admin" password: "1234567" code: "6" exp: "用户不存在/密码错误" case_004: # 测试验证码的场景 username: "admin" password: "123456" code: "7" exp: "验证码错误"#这里的验证码统一设置为6
3.脚本scripts
对于大多数的登录都有包含验证码,该场景为图片验证码。但不管是哪种类型的验证码,都需要先请求验证码,获得验证码的唯一标识符,传递给后续的登陆操作。
from utils.path_utils import yaml_file
from utils.yaml_utils import YamlUtils
def get_uuid():
# 发送 HTTP GET 请求,获取验证码图片标识符
captchaimage = requests.get(url='http://192.168.9.131/api/captchaImage')
# 解析http响应的json数据
res_uuid = captchaimage.json()
# 从json数据中获取uuid
uuid = res_uuid['uuid']
return uuid
class TestUserLogin:
# 使用 parametrize 装饰器,定义参数化测试用例。
# 从 YAML 文件中读取登录测试用例数据,以字典的形式传入测试方法中。
@pytest.mark.parametrize("user_info",
YamlUtils.read_yaml(yaml_file("wms_login_data.yaml"),
"case_login"))
def test_login(self, user_info):
info_msg = {"username": user_info["username"],
"password": user_info["password"],
"code": user_info["code"],
"uuid": get_uuid()}
# 请求登陆页面
response = requests.post(url="http://192.168.9.131/api/login", json=info_msg)
res = response.json()
# 打印响应结果
print(res)
4.测试报告report
使用allure生成测试报告,其实就是装饰器的使用。在上面脚本的基础上添加装饰器:
# 使用 allure 库的 feature 装饰器,定义测试用例所属的特性
@allure.feature("wms仓管系统-登陆接口")
class TestUserLogin:
# 使用 allure 库的 description 装饰器,定义测试用例的描述。
@allure.description("接口测试case")
# 使用 allure 库的 severity 装饰器,定义测试用例的严重性级别。
@allure.severity("Critical")
测试报告:
5.日志log
可以在判断结果的时候,根据不同结果打印不同日志信息,因为日志本身就是用来记录程序执行信息,帮助我们定位错误来源。
if res["msg"] == user_info["exp"]:
logger.info("接口响应符合预期,用例执行成功,请求参数是:%s", info_msg)
else:
logger.error("用例执行失败:直接结果是:%s", res)
raise AssertionError("响应结果不在预期结果内!")
【补充】工具类:数据库连接
当我们的请求需要访问数据库时,可直接调用该类及方法,不需要重复书写冗余代码。【该场景中未使用】
import pymysql
class DB_utils:
# 初始化方法 封装连接和游标
def __init__(self):
try:
self.conn = pymysql.connect(host="192.168.9.131",
port=80,
user="root",
passwd="root",
db="wms")
self.cursor = self.conn.cursor()
print("数据连接成功!")
except Exception as error:
print("数据库连接失败!")
raise error
# 关闭连接和游标
def close(self):
self.cursor.close()
self.conn.close()