feat(session): 项目基本完成

- 新增热加载模块funcs.py
- 新增文件加载模块files.py
- 新增了日志打印
- 新增其他功能
This commit is contained in:
2025-02-23 22:46:33 +08:00
parent 129c845bd8
commit 913bb3f396
25 changed files with 989 additions and 2 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
# created by virtualenv automatically
.idea/
.venv/
poetry.lock
poetry.lock
.pytest_cache/
+54
View File
@@ -0,0 +1,54 @@
# interfaceAutoTest
## 简介
...
## 技术特点
...
## 环境搭建
...
## 使用方法
### 1,创建测试项目
### 2,创建测试文件
- test_开头
- 文件以名字排序,并决定执行顺序
- 文件后缀.yaml
### 3,编写用例内容
**必填字段**
| 字段名 | 用途 | 备注 |
|----------|------|------------------|
| title | 用例名称 | |
| request | 请求参数 | |
| extract | 遍历提取 | 保存在extract.yaml中 |
| validate | 接口断言 | 断言定义在CaseInfo中 |
**选填字段**
| 字段名 | 用途 | 备注 |
|-------------|--------|----|
| parametrize | 数据驱动测试 | |
| epic | 项目名称 | |
| feature | 模块名称 | |
| story | 功能名称 | |
**示例**
```yaml
...
```
### 4,执行用例
+51
View File
@@ -0,0 +1,51 @@
feature: 特征
story: 事件
title: 查询用户信息
request:
method: get
url: http://119.91.19.171:40065/answer/api/v1/connector/info
headers:
Accept-Encoding: gzip, deflate
Accept-Language: zh_CN
Content-Type: application/json
Cookie: psession=33c6c2de-7e5d-40e2-9bbc-3c637a690c3f; lang=zh-CN; 3x-ui=MTcyNjU2NDcwOHxEWDhFQVFMX2dBQUJFQUVRQUFCMV80QUFBUVp6ZEhKcGJtY01EQUFLVEU5SFNVNWZWVk5GVWhoNExYVnBMMlJoZEdGaVlYTmxMMjF2WkdWc0xsVnpaWExfZ1FNQkFRUlZjMlZ5QWYtQ0FBRUVBUUpKWkFFRUFBRUlWWE5sY201aGJXVUJEQUFCQ0ZCaGMzTjNiM0prQVF3QUFRdE1iMmRwYmxObFkzSmxkQUVNQUFBQUdQLUNGUUVDQVFkNGRXa3lNREkwQVFkNGRXa3lNREkwQUE9PXwLOhLRIDjzvQ3oI-UF-GhkMheEENkxRJ8GkAZ79eFHvg==
Host: 119.91.19.171:40065
Origin: http://119.91.19.171:40065
Referer: http://119.91.19.171:40065/users/login
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,like
Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0
extract: # 提取变量
code:
- "json"
- "$.code"
- 0
msg:
- "json"
- "$.msg"
- 0
validate:
equals: # 断言相等
状态码等于200:
- 200
- ${code}
not_equals: # 断言不相等
状态码不等于404:
- 404
- ${code}
contains: # 断言包含
包含关系:
- 404
- ${code}
not_contains: # 断言不包含
不包含关系:
- 404
- ${code}
parametrize: # 数据驱动测试
- [ "title","username","password","code" ] # 变量名
- [ "测试1","user1","pass1","code1" ] # 变量值
- [ "测试2","user2","pass2","code2" ] # 变量值
- [ "测试3","user3","pass3","code3" ] # 变量值
- [ "测试4","user4","pass4","code4" ] # 变量值
+15
View File
@@ -0,0 +1,15 @@
title: 查询用户信息
request:
method: get
url: "https://api.kuleu.com/api/action"
headers:
user-agent: 'Mozilla / 5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 128.0.0.0Safari / 537.36'
params:
text: ${url_unquote(code)}
# data: ${code}
extract:
status_code: [ json, $.data,0 ]
validate:
codes: 200
+30
View File
@@ -0,0 +1,30 @@
title: 查询用户信息
request:
method: get
url: http://119.91.19.171:40065/answer/api/v1/connector/info
headers:
Accept-Encoding: gzip, deflate
Accept-Language: zh_CN
Content-Type: application/json
Cookie: psession=33c6c2de-7e5d-40e2-9bbc-3c637a690c3f; lang=zh-CN; 3x-ui=MTcyNjU2NDcwOHxEWDhFQVFMX2dBQUJFQUVRQUFCMV80QUFBUVp6ZEhKcGJtY01EQUFLVEU5SFNVNWZWVk5GVWhoNExYVnBMMlJoZEdGaVlYTmxMMjF2WkdWc0xsVnpaWExfZ1FNQkFRUlZjMlZ5QWYtQ0FBRUVBUUpKWkFFRUFBRUlWWE5sY201aGJXVUJEQUFCQ0ZCaGMzTjNiM0prQVF3QUFRdE1iMmRwYmxObFkzSmxkQUVNQUFBQUdQLUNGUUVDQVFkNGRXa3lNREkwQVFkNGRXa3lNREkwQUE9PXwLOhLRIDjzvQ3oI-UF-GhkMheEENkxRJ8GkAZ79eFHvg==
Host: 119.91.19.171:40065
Origin: http://119.91.19.171:40065
Referer: http://119.91.19.171:40065/users/login
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,like
Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0
extract: # 提取变量
reason:
- "json"
- "$.reason"
- 0
validate:
# 断言 sql
contains: # 断言包含
用户在数据库中:
- "ltcs"
- ${sql(select username from user where id=1)}
not_contains: # 断言包含
用户不存在在数据库中:
- "ltcs"
- ${sql(select username from user where id=1)}
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: a_test_case.py
@date: 2024 2024/9/15 19:15
@desc:
"""
from requests import Session
import requests
session = Session()
def test_1():
base_url = "https://jsonplaceholder.typicode.com"
session.params = {
'Content-Type': 'application/json;charset=utf-8'
}
url = f"{base_url}/users"
payload = {}
# response = requests.request("POST", url, headers=headers, data=payload)
response = session.get(url, json=payload)
print(response.json()[0]["username"])
assert response.status_code == 200
def test_2():
base_url = r'https://api.kuleu.com/api/action'
params = {"text": "爱情"}
header = {
"user-agent": 'Mozilla / 5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, like Gecko) '
'Chrome / 128.0.0.0Safari / 537.36'
}
response = requests.get(base_url, headers=header, params=params)
# print(response.text)
print(response.json())
print(response.request.url)
assert response.status_code == 200
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: api.py
@date: 2024 2024/9/12 22:52
@desc:
"""
from commons.session import Session
# session = requests.session()
session = Session("https://jsonplaceholder.typicode.com")
session.params = {
'Content-Type': 'application/json;charset=utf-8'
}
url = "/users"
payload = {}
# response = requests.request("POST", url, headers=headers, data=payload)
response = session.get(url, json=payload)
# print(response.text)
# print(response.url)
# print(response)
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: __init__.py.py
@date: 2024 2024/9/15 21:13
@desc:
"""
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: cases.py
@date: 2024 2024/9/16 9:57
@desc: 动态生成用例
"""
from pathlib import Path
import logging
import allure
import pytest
from commons.files import YamlFile
from commons.models import CaseInfo
from commons.session import Session
from commons.exchange import Exchange
from commons import settings
logger = logging.getLogger(__name__)
session = Session(settings.base_url)
_case_path = Path(settings.case_path)
exchanger = Exchange(settings.exchanger)
@allure.epic("项目名称:answer")
class TestAPI:
...
@classmethod
def find_yaml_case(cls, case_path: Path = _case_path):
"""
搜索和加载yaml文件
:return:
"""
yaml_path_list = case_path.glob("**/test_*.yaml") # 搜索当前目录及其子目录下以test_开头yaml为后缀的文件
for yaml_path in yaml_path_list:
logger.info(f"load file {yaml_path=}")
file = YamlFile(yaml_path) # 自动读取yaml文件
case_info = CaseInfo(**file) # 校验yaml格式
logger.debug(f"case_info={case_info.to_yaml()}") # 把case_info 转成字符串,然后记录日志
case_func = cls.new_case(case_info) # 从yaml格式转换为pytest格式
print(yaml_path.name)
setattr(cls, f"{yaml_path.name}", case_func) # 把pytest格式添加到类中
@classmethod
def new_case(cls, case_info: CaseInfo):
ddt_data = case_info.ddt()
print(ddt_data)
ddt_title = [data.title for data in ddt_data]
@allure.feature(case_info.feature)
@allure.story(case_info.story)
@pytest.mark.parametrize("case_info", ddt_data, ids=ddt_title)
def test_func(self, case_info: CaseInfo):
allure.dynamic.title(case_info.title)
logger.info(f"用例开始执行:{case_info.title}".center(80, "="))
# 0,变量替换
new_case_info = exchanger.replace(case_info)
logger.info(f"1,正在注入变量...")
# 1,发送请求
logger.info(f"2,正在请求接口...")
resp = session.request(**new_case_info.request)
logger.info(f"3,正在提取变量...")
# 2,保存变量(接口关联)
for var_name, extract_info in new_case_info.extract.items():
print(var_name, extract_info)
exchanger.extract(resp, var_name, *extract_info)
# 3,断言
logger.info(f"4,正在断言...")
assert_case_info = exchanger.replace(case_info) # 为断言加载变量
print(assert_case_info)
assert_case_info.assert_all() # 执行断言
logger.info(f"用例执行结束:{case_info.title}".center(80, "="))
return test_func
# TestAPI.find_yaml_case()
if __name__ == '__main__':
TestAPI.find_yaml_case()
# print(TestAPI.__dict__)
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: CNWei
@Software: PyCharm
@contact: [email protected]
@file: databases
@date: 2025/2/16 20:53
@desc:
"""
import logging
import pymysql as MySQLdb
from commons import settings
logger = logging.getLogger(__name__)
class DBServer:
def __init__(self, host, port, user, password, database):
self.db = MySQLdb.connect(host=host, port=port, user=user, password=password, database=database)
self.cursor = self.db.cursor() # 创建新的会话
def execute_sql(self, sql):
logger.info(f"执行sql:{sql}")
self.cursor.execute(sql) # 执行sql命令
# res = self.cursor.fetchone() # 返回单行结果
res = self.cursor.fetchall() # 返回多行结果
return res
db = DBServer(
host=settings.db_host, # ip
port=3306, # 端口
user='root', # 用户名
password='mysql_hNahSe', # 密码
database='answer' # 库名
)
if __name__ == '__main__':
...
res = db.execute_sql('select username from user where id=1;')
print(res[0])
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: exchange.py
@date: 2024 2024/9/18 21:58
@desc:
"""
import copy
import json
import logging
import re
import allure
from commons.templates import Template
import jsonpath
from commons.files import YamlFile
from commons.models import CaseInfo
logger = logging.getLogger(__name__)
class Exchange:
def __init__(self, path):
self.file = YamlFile(path)
@allure.step("提取变量")
def extract(self, resp, var_name, attr, expr: str, index):
# resp中json是方法不是属性,需要手动更改为属性
resp = copy.deepcopy(resp)
try:
resp.json = resp.json()
except json.decoder.JSONDecodeError:
resp.json = {"msg": "is not json data"}
data = getattr(resp, attr)
# print(data)
if expr.startswith("/"): # xpath
res = None
elif expr.startswith("$"): # jsonpath
data = dict(data)
res = jsonpath.jsonpath(data, expr)
else: # 正则
res = re.findall(expr, str(data))
# print(res)
if res: # 如果有数据
value = res[index]
else: # 如果没有数据
value = "not data"
logger.debug(f"{var_name} = {value}") # 记录变量名和变量值
self.file[var_name] = value # 保存变量
self.file.save() # 持久化存储到文件
@allure.step("替换变量")
def replace(self, case_info: CaseInfo):
...
# 1,将case_info转换为字符串
case_info_str = case_info.to_yaml()
# 2,替换字符串
case_info_str = Template(case_info_str).render(self.file)
# 3,将字符串转换成case_info
new_case_info = case_info.by_yaml(case_info_str)
return new_case_info
if __name__ == '__main__':
class MockResponse:
text = '{"name":"张三","age":"18","data":[3,4,5],"aaa":null}'
def json(self):
return json.loads(self.text)
mock_resp = MockResponse()
# print(mock_resp.text)
# print(mock_resp.json())
exchanger = Exchange(r"E:\PyP\InterfaceAutoTest\extract.yaml")
exchanger.extract(mock_resp, "name", "json", '$.name', 0)
exchanger.extract(mock_resp, "age", "json", '$.age', 0)
exchanger.extract(mock_resp, "data", "json", '$.data', 0)
exchanger.extract(mock_resp, "aaa", "json", '$.aaa', 0)
case_info = CaseInfo(
title="单元测试",
request={
"data":
{"name": "${name}", "age": "${str(age)}", "time": "${add(1,2)}"}
},
extract={},
validate={}
)
new_case_info = exchanger.replace(case_info)
print(new_case_info)
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: files.py
@date: 2024 2024/9/15 21:28
@desc: 读取和保存yaml文件
"""
import logging
import yaml
from commons.models import CaseInfo
logger = logging.getLogger(__name__)
class YamlFile(dict):
def __init__(self, path):
super().__init__()
self.path = path
self.load()
def load(self):
with open(self.path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f) # 字典
if data:
self.update(data) # 把两个字段的内容合并
def save(self):
with open(self.path, "w", encoding="utf-8") as f:
yaml.safe_dump(
dict(self),
stream=f,
allow_unicode=True, # allow_unicode:使用unicode编码正常显示中文
sort_keys=False) # sort_keys:保持原有排序
if __name__ == '__main__':
yaml_path = r'E:\PyP\InterfaceAutoTest\TestCases\test_1_user.yaml'
yaml_file = YamlFile(yaml_path)
# yaml_file.load()
case_info = CaseInfo(**yaml_file)
yaml_file["title"] = "查询用户信息"
yaml_file.save()
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: funcs.py
@date: 2024 2024/9/22 22:46
@desc:
"""
import base64
import logging
import time
import urllib.parse
import hashlib
from commons.databases import db
# from commons.files import YamlFile
from commons import settings
logger = logging.getLogger(__name__)
def url_unquote(s: str) -> str:
return urllib.parse.unquote(s)
def time_str() -> str:
return str(time.time())
def add(a, b):
return str(int(a) + int(b))
def sql(s: str) -> str:
res = db.execute_sql(s)
return res[0][0]
def new_id():
# 自增,永不重复
id_file = YamlFile(settings.id_path)
id_file["id"] += 1
id_file.save()
return id_file["id"]
def last_id() -> str:
# 不自增,只返回结果
id_file = YamlFile("id.yaml")
return id_file["id"]
def md5(content: str) -> str:
# 1,原文转为字节
content = content.encode("utf-8")
result = hashlib.md5(content).hexdigest()
return result
def base64_encode(content: str) -> str:
# 1,原文转二进制
content = content.encode("utf-8")
# 2,base64编码(二进制)
encode_value = base64.b64encode(content)
# 3,转为字符串
encode_str = encode_value.decode("utf-8")
return encode_str
def base64_decode(content: str) -> str:
# 1,原文转二进制
content = content.encode("utf-8")
# 2,base64解码(二进制)
decode_value = base64.b64decode(content)
# 3,转为字符串
decode_str = decode_value.decode("utf-8")
return decode_str
def rsa_encode(content: str) -> str:
...
def rsa_decode(content: str) -> str:
...
if __name__ == '__main__':
# res = url_unquote("%E6%88%90%E5%8A%9F%E3%80%82")
# print(res)
a = "这是中文dddddd"
bb = base64_encode(a)
print(bb)
cc = base64_decode(bb)
print(cc)
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: models.py
@date: 2024 2024/9/15 21:14
@desc: 声明yaml用例格式
"""
import logging
from dataclasses import dataclass, asdict
import allure
import yaml
from commons.templates import Template
from commons import settings
logger = logging.getLogger(__name__)
@dataclass
class CaseInfo:
title: str
request: dict
extract: dict
validate: dict
parametrize: list = ""
epic: str = settings.allure_epic
feature: str = settings.allure_feature
story: str = settings.allure_story
def to_yaml(self) -> str:
# 序列化成yaml字符串
yaml_str = yaml.safe_dump(
asdict(self),
allow_unicode=True, # allow_unicode:使用unicode编码正常显示中文
sort_keys=False)
return yaml_str
@classmethod
def by_yaml(cls, yaml_str):
# 反序列化
obj = cls(**yaml.safe_load(yaml_str))
return obj
@allure.step("断言")
def assert_all(self):
if not self.validate:
return
for assert_type, assert_value in self.validate.items():
for msg, data in assert_value.items():
a, b = data[0], data[1]
# print(assert_type, a, b, msg)
match assert_type:
case 'equals':
logger.info(f"assert {a} == {b}, {msg}")
assert a == b, msg
case 'not_equals':
logger.info(f"assert {a} != {b}, {msg}")
assert a != b, msg
case 'contains':
logger.info(f"assert {a} in {b}, {msg}")
assert a in b, msg
case 'not_contains':
logger.info(f"assert {a} not in {b}, {msg}")
assert a not in b, msg
# case "xxxxx
def ddt(self) -> list: # 返回一个列表,列表中应该包含N个注入了变量的caseInfo
case_list = []
if not self.parametrize: # 没有使用数据驱动测试
case_list.append('')
else: # 使用数据驱动测试
args_name = self.parametrize[0]
args_value_list = self.parametrize[1:]
for args_value in args_value_list:
d = dict(zip(args_name, args_value))
# d 就是数据驱动测试的变量,应输入到用例中
case_info_str = self.to_yaml() # 转字符串
case_info_str = Template(case_info_str).render(d) # 输入变量
case_info = self.by_yaml(case_info_str) # 转成类
case_list.append(case_info) # 加入到返回值
return case_list
if __name__ == '__main__':
with open(r'E:\PyP\InterfaceAutoTest\TestCases\test_1_user.yaml', encoding='utf-8') as f:
data = yaml.safe_load(f)
# print(data)
case_info = CaseInfo(**data)
s = case_info.to_yaml()
print(s)
new_case_info = case_info.by_yaml(s)
print(new_case_info)
+3 -1
View File
@@ -12,6 +12,7 @@
from urllib.parse import urljoin
import logging
import requests
import allure
from requests import Response, PreparedRequest
@@ -24,6 +25,7 @@ class Session(requests.Session):
super().__init__() # 先执行父类的初始化
self.base_url = base_url # 在执行子类的初始化操作
@allure.step("发送请求")
def request(self, method, url: str, *args, **kwargs) -> Response:
if not url.startswith("http"):
# 自动添加baseurl
@@ -39,7 +41,7 @@ class Session(requests.Session):
logger.info(f"接收响应 <<<<<< 状态码 = {resp.status_code}")
logger.info(f"接收响应 <<<<<< 响应头 = {resp.headers}")
logger.info(f"接收响应 <<<<<< 响应正文 = {resp.content}")
logger.info(f"接收响应 <<<<<< 响应正文 = {resp.json()}")
return resp
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: CNWei
@Software: PyCharm
@contact: [email protected]
@file: settings
@date: 2025/2/23 21:34
@desc:
"""
base_url = 'http://127.0.0.1:8000'
case_path = r"E:\PyP\InterfaceAutoTest\TestCases"
exchanger = r"E:\PyP\InterfaceAutoTest\extract.yaml"
id_path =r"E:\PyP\InterfaceAutoTest\id.yaml"
db_host = '119.91.19.171' # ip
db_port = 3306 # 端口
db_user = 'root' # 用户名
db_password = 'mysql_hNahSe' # 密码
db_database = 'answer' # 库名
allure_epic: str = "项目名称:answer"
allure_feature: str = "默认特征(feature)"
allure_story: str = "默认事件(story)"
rsa_public = ""
rsa_private = ""
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: templates.py
@date: 2024 2024/9/22 22:20
@desc:
"""
import copy
import logging
import re
import string
logger = logging.getLogger(__name__)
def _str(s) -> str:
# 将数据转换为str类型。
return f"'{s}'"
class Template(string.Template):
"""
1,支持函数调用
2,参数也可以是变量
"""
func_mapping = {
"str": _str,
"int": int,
"float": float,
"bool": bool
} # 内置函数有的,直接放入mapping;内置函数没有的,在funcs中定义,自动放入mapping
call_pattern = re.compile(r"\${(?P<func_name>.*?)\((?P<func_args>.*?)\)}")
def render(self, mapping: dict) -> str:
s = self.safe_substitute(mapping) # 原有方法替换变量
s = self.safe_substitute_funcs(s, mapping)
return s
def safe_substitute_funcs(self, template, mapping) -> str:
"""
解析字符串中的函数名和参数,并将函数调用结果进行替换
:param template: 字符串
:param mapping: 上下文,提供要使用的函数和变量
:return: 替换后的结果
"""
mapping = copy.deepcopy(mapping)
mapping.update(self.func_mapping) # 合并两个mapping
def convert(mo):
func_name = mo.group("func_name")
func_args = mo.group("func_args").split(",")
func = mapping.get(func_name) # 读取指定函数
func_args_value = [mapping.get(arg, arg) for arg in func_args]
if func_args_value == [""]: # 处理没有参数的func
func_args_value = []
if not callable(func):
return mo.group() # 如果是不可调用的假函数,不进行替换
else:
return str(func(*func_args_value)) # 否则用函数结果进行替换
return self.call_pattern.sub(convert, template)
def hot_load():
from commons import funcs
for func_name in dir(funcs): # 遍历模块中的所有函数
if func_name.startswith("_"):
continue
func_code = getattr(funcs, func_name) # 取到函数对象
if callable(func_code): # 如果是一个可以调用的函数
Template.func_mapping[func_name] = func_code # 函数放到Template中
hot_load()
+3
View File
@@ -0,0 +1,3 @@
code: 200
msg: 成功。
reason: base.success
+1
View File
@@ -0,0 +1 @@
"id":0
+18
View File
@@ -0,0 +1,18 @@
''02/23/2025 10:17:34 PM' [commons.cases] INFO cases.find_yaml_case:44 - load file yaml_path=WindowsPath('E:/PyP/InterfaceAutoTest/TestCases/test_1_user.yaml')'
''02/23/2025 10:17:34 PM' [commons.cases] INFO cases.find_yaml_case:44 - load file yaml_path=WindowsPath('E:/PyP/InterfaceAutoTest/TestCases/test_2_url.yaml')'
''02/23/2025 10:17:34 PM' [commons.cases] INFO cases.find_yaml_case:44 - load file yaml_path=WindowsPath('E:/PyP/InterfaceAutoTest/TestCases/test_3_sql.yaml')'
''02/23/2025 10:17:34 PM' [pytest_result_log] INFO plugin.pytest_runtest_setup:122 - ---------------Start: main.py::TestAPI::test_1_user.yaml[查询用户信息0]---------------'
''02/23/2025 10:17:34 PM' [commons.cases] INFO cases.test_func:67 - =================================用例开始执行:查询用户信息=================================='
''02/23/2025 10:17:34 PM' [commons.cases] INFO cases.test_func:71 - 1,正在注入变量...'
''02/23/2025 10:17:34 PM' [commons.cases] INFO cases.test_func:74 - 2,正在请求接口...'
''02/23/2025 10:17:34 PM' [requests.session] INFO session.send:36 - 发送请求>>>>>> 接口地址 = GET http://119.91.19.171:40065/answer/api/v1/connector/info'
''02/23/2025 10:17:34 PM' [requests.session] INFO session.send:37 - 发送请求>>>>>> 请求头 = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Accept-Language': 'zh_CN', 'Content-Type': 'application/json', 'Cookie': 'psession=33c6c2de-7e5d-40e2-9bbc-3c637a690c3f; lang=zh-CN; 3x-ui=MTcyNjU2NDcwOHxEWDhFQVFMX2dBQUJFQUVRQUFCMV80QUFBUVp6ZEhKcGJtY01EQUFLVEU5SFNVNWZWVk5GVWhoNExYVnBMMlJoZEdGaVlYTmxMMjF2WkdWc0xsVnpaWExfZ1FNQkFRUlZjMlZ5QWYtQ0FBRUVBUUpKWkFFRUFBRUlWWE5sY201aGJXVUJEQUFCQ0ZCaGMzTjNiM0prQVF3QUFRdE1iMmRwYmxObFkzSmxkQUVNQUFBQUdQLUNGUUVDQVFkNGRXa3lNREkwQVFkNGRXa3lNREkwQUE9PXwLOhLRIDjzvQ3oI-UF-GhkMheEENkxRJ8GkAZ79eFHvg==', 'Host': '119.91.19.171:40065', 'Origin': 'http://119.91.19.171:40065', 'Referer': 'http://119.91.19.171:40065/users/login'}'
''02/23/2025 10:17:34 PM' [requests.session] INFO session.send:38 - 发送请求>>>>>> 请求正文 = None '
''02/23/2025 10:17:34 PM' [requests.session] INFO session.send:42 - 接收响应 <<<<<< 状态码 = 200'
''02/23/2025 10:17:34 PM' [requests.session] INFO session.send:43 - 接收响应 <<<<<< 响应头 = {'Content-Type': 'application/json; charset=utf-8', 'Date': 'Sun, 23 Feb 2025 14:17:34 GMT', 'Content-Length': '64'}'
''02/23/2025 10:17:34 PM' [requests.session] INFO session.send:44 - 接收响应 <<<<<< 响应正文 = {'code': 200, 'reason': 'base.success', 'msg': '成功。', 'data': []}'
''02/23/2025 10:17:34 PM' [commons.cases] INFO cases.test_func:77 - 3,正在提取变量...'
''02/23/2025 10:17:34 PM' [commons.cases] INFO cases.test_func:83 - 4,正在断言...'
''02/23/2025 10:17:34 PM' [commons.models] INFO models.assert_all:59 - assert 200 == code1, 状态码等于200'
''02/23/2025 10:17:34 PM' [pytest_result_log] ERROR plugin.pytest_result_log:190 - test status is FAILED (main.py::TestAPI::test_1_user.yaml[查询用户信息0]): AssertionError'
''02/23/2025 10:17:34 PM' [pytest_result_log] INFO plugin.pytest_runtest_teardown:128 - ----------------End: main.py::TestAPI::test_1_user.yaml[查询用户信息0]----------------'
+64
View File
@@ -0,0 +1,64 @@
import os
import time
from logging.handlers import TimedRotatingFileHandler
class LufffyTimedRotatingFileHandler(TimedRotatingFileHandler):
def doRollover(self):
"""
do a rollover; in this case, a date/time stamp is appended to the filename
when the rollover happens. However, you want the file to be named for the
start of the interval, not the current time. If there is a backup count,
then we have to get a list of matching filenames, sort them and remove
the one with the oldest suffix.
"""
if self.stream:
self.stream.close()
self.stream = None
# get the time that this sequence started at and make it a TimeTuple
currentTime = int(time.time())
dstNow = time.localtime(currentTime)[-1]
t = self.rolloverAt - self.interval
if self.utc:
timeTuple = time.gmtime(t)
else:
timeTuple = time.localtime(t)
dstThen = timeTuple[-1]
if dstNow != dstThen:
if dstNow:
addend = 3600
else:
addend = -3600
timeTuple = time.localtime(t + addend)
"""
dfn = self.rotation_filename(self.baseFilename + "." +
time.strftime(self.suffix, timeTuple))
if os.path.exists(dfn):
os.remove(dfn)
self.rotate(self.baseFilename, dfn)
"""
# 多进程会导致误删日志,将上面代码重写为如下代码(判断如果不存在则重命名)
# 注意:如果改写的代码会影响其他模块则不能采用该方法
dfn = self.rotation_filename(self.baseFilename + "." +
time.strftime(self.suffix, timeTuple))
if not os.path.exists(dfn):
self.rotate(self.baseFilename, dfn)
if self.backupCount > 0:
for s in self.getFilesToDelete():
os.remove(s)
if not self.delay:
self.stream = self._open()
newRolloverAt = self.computeRollover(currentTime)
while newRolloverAt <= currentTime:
newRolloverAt = newRolloverAt + self.interval
#If DST changes and midnight or weekly rollover, adjust for this.
if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
dstAtRollover = time.localtime(newRolloverAt)[-1]
if dstNow != dstAtRollover:
if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
addend = -3600
else: # DST bows out before next rollover, so we need to add an hour
addend = 3600
newRolloverAt += addend
self.rolloverAt = newRolloverAt
+18
View File
@@ -0,0 +1,18 @@
import os
import shutil
import datetime
import pytest
from commons.cases import TestAPI
TestAPI.find_yaml_case() # 加载yaml文件
if __name__ == '__main__':
now = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
# 1,启动框架(生成临时文件)
pytest.main([__file__, "-x", "-v"]) # -x表示有一个用例失败后面将不执行;-v表示展示用例名称;-c,配置文件所在目录:指定pytest.ini路径
# 2,生成HTML报告
os.system('allure generate temp -o report --clean') # java程序只能借助操作系统执行
# 3,备份日志
shutil.copy2("logs/pytest.log", f"logs/pytest_{now}.log")
+6
View File
@@ -10,6 +10,12 @@ python = "^3.10"
requests = "^2.32.3"
pyyaml = "^6.0.2"
pytest = "^8.3.3"
jsonpath = "^0.82.2"
pymysql = "^1.1.1"
pytest-result-log = "^1.2.2"
allure-pytest = "^2.13.5"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
+10
View File
@@ -0,0 +1,10 @@
[pytest]
addopts = -q --show-capture=no
log_file = logs/pytest.log
log_file_level = info
log_file_format = '%(asctime)s [%(name)s] %(levelname)s %(module)s.%(funcName)s:%(lineno)d - %(message)s'
log_file_date_format = '%m/%d/%Y %I:%M:%S %p'
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env python
# coding=utf-8
"""
@author: chen wei
@Software: PyCharm
@contact: [email protected]
@file: header_transition.py
@date: 2024 2024/9/17 17:34
@desc:
"""
import re
headerStr = '''
Accept-Encoding: gzip, deflate
Accept-Language: zh_CN
Content-Type: application/json
Cookie: psession=33c6c2de-7e5d-40e2-9bbc-3c637a690c3f; lang=zh-CN; 3x-ui=MTcyNjU2NDcwOHxEWDhFQVFMX2dBQUJFQUVRQUFCMV80QUFBUVp6ZEhKcGJtY01EQUFLVEU5SFNVNWZWVk5GVWhoNExYVnBMMlJoZEdGaVlYTmxMMjF2WkdWc0xsVnpaWExfZ1FNQkFRUlZjMlZ5QWYtQ0FBRUVBUUpKWkFFRUFBRUlWWE5sY201aGJXVUJEQUFCQ0ZCaGMzTjNiM0prQVF3QUFRdE1iMmRwYmxObFkzSmxkQUVNQUFBQUdQLUNGUUVDQVFkNGRXa3lNREkwQVFkNGRXa3lNREkwQUE9PXwLOhLRIDjzvQ3oI-UF-GhkMheEENkxRJ8GkAZ79eFHvg==
Host: 119.91.19.171:40065
Origin: http://119.91.19.171:40065
Referer: http://119.91.19.171:40065/users/login
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0
'''
ret = ""
for i in headerStr:
if i == '\n':
i = "',\n"
ret += i
ret = re.sub(": ", ": '", ret)
print(ret[3: -3])