程说明图
这张图是我的一些设计思路。
在yaml文件中管理相关的数据即可实现接口测试。
采用的接口是智学网
网站的API。
支持token
认证
框架体系介绍
目录/文件 | 说明 | 是否为python 包 |
---|---|---|
apiData | 存放测试信息和用例的yaml 文件目录 |
|
config | 配置目录,目录配置,allure环境变量配置 | 是 |
common | 公共类,封装读取yaml 文件 |
是 |
core | 封装requests 等常用方法 |
是 |
logs | 日志文件 | |
tests | 测试用例 | 是 |
utils | 工具类,日志等 | 是 |
pytest.ini | pytest配置文件 | |
run.bat | 执行脚本 |
配置用例信息
经过excel和yaml的对比,最终我选择了yaml文件管理用例信息。
BusinessInterface.yaml
业务接口测试
登录验证:
method: post
route: /loginSuccess/
RequestData:
data:
userId: "{{data}}"
expectcode: 200
regularcheck:
resultcheck: '"result":"success"'
stand_alone_interface.yaml
单个接口测试
登录:
method: post
route: /weakPwdLogin/?from=web_login
RequestData:
data:
loginName: 18291900215
password: dd636482aca022
code:
description: encrypt
expectcode: 200
regularcheck: '[d]{16}'
resultcheck: '"result":"success"'
extractresult:
- data
配置测试信息
testInfo.yaml
测试信息配置
test_info: # 测试信息
url: https://www.zhixue.com
timeout: 30.0
headers:
Accept: application/json, text/javascript, */*; q=0.01
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
cookies: aliyungf_tc=AQAAANNdlkvZ2QYAIb2Q221oiyiSOfhg; tlsysSessionId=cf0a8657-4a02-4a40-8530-ca54889da838; isJump=true; deviceId=27763EA6-04F9-4269-A2D5-59BA0FB1F154; 6e12c6a9-fda1-41b3-82ec-cc496762788d=webim-visitor-69CJM3RYGHMP79F7XV2M; loginUserName=18291900215
X-Requested-With: XMLHttpRequest
读取信息
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
from ruamel import yaml
from config.conf import DATA_DIR
class ApiInfo:
"""接口信息"""
def __init__(self):
self.info_path = os.path.join(DATA_DIR, 'testinfo.yaml')
self.business_path = os.path.join(DATA_DIR, 'BusinessInterface.yaml')
self.stand_alone_path = os.path.join(DATA_DIR, 'stand_alone_interface.yaml')
@classmethod
def load(cls, path):
with open(path, encoding='utf-8') as f:
return yaml.safe_load(f)
@property
def info(self):
return self.load(self.info_path)
@property
def business(self):
return self.load(self.business_path)
@property
def stand_alone(self):
return self.load(self.stand_alone_path)
def test_info(self, value):
"""测试信息"""
return self.info['test_info'][value]
def login_info(self, value):
"""登录信息"""
return self.stand_alone['登录'].get(value)
def case_info(self, name):
"""用例信息"""
return self.business[name]
def stand_info(self, name):
"""单个接口"""
return self.stand_alone[name]
testinfo = ApiInfo()
if __name__ == '__main__':
print(testinfo.info['test_info'])
封装日志
logger.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
import logging
from config import conf
from datetime import datetime
class Logger:
def __init__(self, name):
self.logger = logging.getLogger(name)
if not self.logger.handlers:
self.logger.setLevel(logging.DEBUG)
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler(self.log_path, encoding='utf-8')
fh.setLevel(logging.DEBUG)
# 在控制台输出
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
# 定义hanler的格式
formatter = logging.Formatter(self.fmt)
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给log添加handles
self.logger.addHandler(fh)
self.logger.addHandler(ch)
@property
def fmt(self):
return '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s'
@property
def log_path(self):
if not os.path.exists(conf.LOG_PATH):
os.makedirs(conf.LOG_PATH)
month = datetime.now().strftime("%Y%m")
return os.path.join(conf.LOG_PATH, '{}.log'.format(month))
log = Logger('root').logger
if __name__ == '__main__':
log.info("你好")
封装requests
request.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import json
import allure
import urllib3
import requests
from utils.logger import log
from requests import Response
from requests.status_codes import codes
from requests.exceptions import RequestException
from common.ApiData import testinfo
from common.RegExp import regexps
from core.serialize import deserialization, serialization
from core.getresult import get_result
urllib3.disable_warnings()
__all__ = ['req', 'codes']
class HttpRequest(object):
"""requests方法二次封装"""
def __init__(self):
self.timeout = 30.0
self.r = requests.session()
self.headers = testinfo.test_info('headers')
def send_request(self, method: str, route: str, extract: str, **kwargs):
"""发送请求
:param method: 发送方法
:param route: 发送路径
optional 可选参数
:param extract: 要提取的值
:param params: 发送参数-"GET"
:param data: 发送表单-"POST"
:param json: 发送json-"post"
:param headers: 头文件
:param cookies: 验证字典
:param files: 上传文件,字典:类似文件的对象``
:param timeout: 等待服务器发送的时间
:param auth: 基本/摘要/自定义HTTP身份验证
:param allow_redirects: 允许重定向,默认为True
:type bool
:param proxies: 字典映射协议或协议和代理URL的主机名。
:param stream: 是否立即下载响应内容。默认为“False”。
:type bool
:param verify: (可选)一个布尔值,在这种情况下,它控制是否验证服务器的TLS证书或字符串,在这种情况下,它必须是路径到一个CA包使用。默认为“True”。
:type bool
:param cert: 如果是字符串,则为ssl客户端证书文件(.pem)的路径
:return: request响应
"""
pass
method = method.upper()
url = testinfo.test_info('url') + route
try:
log.info("Request Url: {}".format(url))
log.info("Request Method: {}".format(method))
if kwargs:
kwargs_str = serialization(kwargs)
is_sub = regexps.findall(kwargs_str)
if is_sub:
new_kwargs_str = deserialization(regexps.subs(is_sub, kwargs_str))
log.info("Request Data: {}".format(new_kwargs_str))
kwargs = new_kwargs_str
log.info("Request Data: {}".format(kwargs))
if method == "GET":
response = self.r.get(url, **kwargs, headers=self.headers, timeout=self.timeout)
elif method == "POST":
response = self.r.post(url, **kwargs, headers=self.headers, timeout=self.timeout)
elif method == "PUT":
response = self.r.put(url, **kwargs, headers=self.headers, timeout=self.timeout)
elif method == "DELETE":
response = self.r.delete(url, **kwargs, headers=self.headers, timeout=self.timeout)
elif method in ("OPTIONS", "HEAD", "PATCH"):
response = self.r.request(method, url, **kwargs, headers=self.headers, timeout=self.timeout)
else:
raise AttributeError("send request method is ERROR!")
with allure.step