235 lines
8.1 KiB
Python
235 lines
8.1 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
文章阅读与界面预览 GET 接口响应速度测试
|
|||
|
|
|
|||
|
|
测试范围:
|
|||
|
|
- 界面预览:config、book/parts、book/all-chapters、book/chapters-by-part
|
|||
|
|
- 文章阅读:book/chapter/:id、book/chapter/by-mid/:mid
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
SOUL_TEST_ENV=soulapi python scripts/test/miniapp/test_article_preview_speed.py
|
|||
|
|
SOUL_TEST_ENV=soulapi python -m scripts.test.miniapp.test_article_preview_speed
|
|||
|
|
|
|||
|
|
产出:控制台报表 + 开发文档/测试报告-文章阅读与界面预览响应速度-YYYYMMDD.md
|
|||
|
|
"""
|
|||
|
|
import json
|
|||
|
|
import sys
|
|||
|
|
import time
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
import requests
|
|||
|
|
|
|||
|
|
# 加载测试配置
|
|||
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|||
|
|
from config import API_BASE, ENV_LABEL, get_env_banner
|
|||
|
|
|
|||
|
|
# 每接口请求次数(取平均)
|
|||
|
|
ROUNDS = 5
|
|||
|
|
TIMEOUT = 30
|
|||
|
|
|
|||
|
|
|
|||
|
|
def measure_get(url: str, desc: str) -> dict:
|
|||
|
|
"""对 GET 请求测速,返回 {ok, status_code, times_ms, avg_ms, min_ms, max_ms, error}"""
|
|||
|
|
times_ms = []
|
|||
|
|
last_error = None
|
|||
|
|
last_status = None
|
|||
|
|
for _ in range(ROUNDS):
|
|||
|
|
t0 = time.perf_counter()
|
|||
|
|
try:
|
|||
|
|
r = requests.get(url, timeout=TIMEOUT)
|
|||
|
|
last_status = r.status_code
|
|||
|
|
elapsed = (time.perf_counter() - t0) * 1000
|
|||
|
|
times_ms.append(elapsed)
|
|||
|
|
if r.status_code != 200:
|
|||
|
|
last_error = f"HTTP {r.status_code}"
|
|||
|
|
except requests.RequestException as e:
|
|||
|
|
last_error = str(e)
|
|||
|
|
times_ms.append(-1)
|
|||
|
|
if not times_ms:
|
|||
|
|
return {"ok": False, "error": last_error or "无响应", "status_code": last_status}
|
|||
|
|
valid = [t for t in times_ms if t >= 0]
|
|||
|
|
return {
|
|||
|
|
"ok": len(valid) == ROUNDS and (last_status or 200) == 200,
|
|||
|
|
"status_code": last_status,
|
|||
|
|
"times_ms": times_ms,
|
|||
|
|
"avg_ms": sum(valid) / len(valid) if valid else 0,
|
|||
|
|
"min_ms": min(valid) if valid else 0,
|
|||
|
|
"max_ms": max(valid) if valid else 0,
|
|||
|
|
"error": last_error,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
print(get_env_banner())
|
|||
|
|
base = API_BASE.rstrip("/")
|
|||
|
|
|
|||
|
|
# 1. 先拉取 parts 和 all-chapters,获取 partId、id、mid
|
|||
|
|
parts_url = f"{base}/api/miniprogram/book/parts"
|
|||
|
|
all_chapters_url = f"{base}/api/miniprogram/book/all-chapters"
|
|||
|
|
|
|||
|
|
parts_data = None
|
|||
|
|
all_chapters_data = None
|
|||
|
|
try:
|
|||
|
|
r = requests.get(parts_url, timeout=TIMEOUT)
|
|||
|
|
if r.status_code == 200:
|
|||
|
|
parts_data = r.json()
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
try:
|
|||
|
|
r = requests.get(all_chapters_url, timeout=TIMEOUT)
|
|||
|
|
if r.status_code == 200:
|
|||
|
|
all_chapters_data = r.json()
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
part_id = None
|
|||
|
|
chapter_id = None
|
|||
|
|
chapter_mid = None
|
|||
|
|
if parts_data and parts_data.get("success"):
|
|||
|
|
parts = parts_data.get("parts") or []
|
|||
|
|
fixed = parts_data.get("fixedSections") or []
|
|||
|
|
if parts:
|
|||
|
|
part_id = parts[0].get("id")
|
|||
|
|
if fixed:
|
|||
|
|
chapter_mid = fixed[0].get("mid")
|
|||
|
|
chapter_id = fixed[0].get("id")
|
|||
|
|
if (not chapter_id or not chapter_mid) and all_chapters_data and all_chapters_data.get("success"):
|
|||
|
|
arr = all_chapters_data.get("data") or all_chapters_data.get("chapters") or []
|
|||
|
|
if arr:
|
|||
|
|
first = arr[0] if isinstance(arr[0], dict) else {}
|
|||
|
|
chapter_id = chapter_id or first.get("id")
|
|||
|
|
chapter_mid = chapter_mid or first.get("mid")
|
|||
|
|
if not part_id and parts_data and parts_data.get("success"):
|
|||
|
|
parts = parts_data.get("parts") or []
|
|||
|
|
if parts:
|
|||
|
|
part_id = parts[0].get("id")
|
|||
|
|
|
|||
|
|
# 2. 定义测试用例(仅 GET)
|
|||
|
|
cases = [
|
|||
|
|
("界面预览-配置", f"{base}/api/miniprogram/config", "GET /api/miniprogram/config"),
|
|||
|
|
("界面预览-目录", f"{base}/api/miniprogram/book/parts", "GET /api/miniprogram/book/parts"),
|
|||
|
|
("界面预览-全书章节", f"{base}/api/miniprogram/book/all-chapters", "GET /api/miniprogram/book/all-chapters"),
|
|||
|
|
]
|
|||
|
|
if part_id:
|
|||
|
|
cases.append(
|
|||
|
|
(
|
|||
|
|
"界面预览-篇章内章节",
|
|||
|
|
f"{base}/api/miniprogram/book/chapters-by-part?partId={part_id}",
|
|||
|
|
f"GET /api/miniprogram/book/chapters-by-part?partId={part_id}",
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
if chapter_id:
|
|||
|
|
cases.append(
|
|||
|
|
(
|
|||
|
|
"文章阅读-按id",
|
|||
|
|
f"{base}/api/miniprogram/book/chapter/{chapter_id}",
|
|||
|
|
f"GET /api/miniprogram/book/chapter/:id",
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
if chapter_mid:
|
|||
|
|
cases.append(
|
|||
|
|
(
|
|||
|
|
"文章阅读-按mid",
|
|||
|
|
f"{base}/api/miniprogram/book/chapter/by-mid/{chapter_mid}",
|
|||
|
|
f"GET /api/miniprogram/book/chapter/by-mid/:mid",
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 3. 执行测速
|
|||
|
|
results = []
|
|||
|
|
for name, url, api_desc in cases:
|
|||
|
|
print(f"\n测速: {name} ({api_desc})")
|
|||
|
|
res = measure_get(url, name)
|
|||
|
|
res["name"] = name
|
|||
|
|
res["api"] = api_desc
|
|||
|
|
res["url"] = url
|
|||
|
|
results.append(res)
|
|||
|
|
if res["ok"]:
|
|||
|
|
print(f" [OK] avg={res['avg_ms']:.0f}ms (min={res['min_ms']:.0f}, max={res['max_ms']:.0f})")
|
|||
|
|
else:
|
|||
|
|
print(f" [FAIL] {res.get('error', res.get('status_code', '?'))}")
|
|||
|
|
|
|||
|
|
# 4. 生成报表
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
date_str = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|||
|
|
date_file = datetime.now().strftime("%Y%m%d")
|
|||
|
|
|
|||
|
|
lines = [
|
|||
|
|
"# 文章阅读与界面预览 GET 接口响应速度测试报告",
|
|||
|
|
"",
|
|||
|
|
f"**测试时间**: {date_str}",
|
|||
|
|
f"**测试环境**: {ENV_LABEL} ({API_BASE})",
|
|||
|
|
f"**每接口请求次数**: {ROUNDS}",
|
|||
|
|
"",
|
|||
|
|
"## 一、测试范围",
|
|||
|
|
"",
|
|||
|
|
"| 分类 | 接口 | 说明 |",
|
|||
|
|
"|------|------|------|",
|
|||
|
|
"| 界面预览 | GET /api/miniprogram/config | 配置(价格、功能开关等) |",
|
|||
|
|
"| 界面预览 | GET /api/miniprogram/book/parts | 目录-篇章列表 |",
|
|||
|
|
"| 界面预览 | GET /api/miniprogram/book/all-chapters | 全书章节列表 |",
|
|||
|
|
"| 界面预览 | GET /api/miniprogram/book/chapters-by-part | 篇章内章节列表 |",
|
|||
|
|
"| 文章阅读 | GET /api/miniprogram/book/chapter/:id | 按业务 id 获取章节内容 |",
|
|||
|
|
"| 文章阅读 | GET /api/miniprogram/book/chapter/by-mid/:mid | 按 mid 获取章节内容 |",
|
|||
|
|
"",
|
|||
|
|
"## 二、响应速度结果",
|
|||
|
|
"",
|
|||
|
|
"| 接口 | 状态 | 平均(ms) | 最小(ms) | 最大(ms) |",
|
|||
|
|
"|------|------|----------|----------|----------|",
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for r in results:
|
|||
|
|
status = "OK" if r["ok"] else "FAIL"
|
|||
|
|
avg = f"{r['avg_ms']:.0f}" if r["ok"] else "-"
|
|||
|
|
min_ms = f"{r['min_ms']:.0f}" if r["ok"] else "-"
|
|||
|
|
max_ms = f"{r['max_ms']:.0f}" if r["ok"] else "-"
|
|||
|
|
if not r["ok"]:
|
|||
|
|
err = r.get("error", "") or f"HTTP {r.get('status_code', '?')}"
|
|||
|
|
avg = err[:20] if err else "-"
|
|||
|
|
lines.append(f"| {r['api']} | {status} | {avg} | {min_ms} | {max_ms} |")
|
|||
|
|
|
|||
|
|
# 汇总
|
|||
|
|
ok_count = sum(1 for r in results if r["ok"])
|
|||
|
|
total_count = len(results)
|
|||
|
|
if ok_count == total_count:
|
|||
|
|
avg_all = sum(r["avg_ms"] for r in results) / total_count
|
|||
|
|
lines.extend([
|
|||
|
|
"",
|
|||
|
|
"## 三、汇总",
|
|||
|
|
"",
|
|||
|
|
f"- 通过: {ok_count}/{total_count}",
|
|||
|
|
f"- 全部接口平均响应: {avg_all:.0f}ms",
|
|||
|
|
"",
|
|||
|
|
])
|
|||
|
|
else:
|
|||
|
|
lines.extend([
|
|||
|
|
"",
|
|||
|
|
"## 三、汇总",
|
|||
|
|
"",
|
|||
|
|
f"- 通过: {ok_count}/{total_count}",
|
|||
|
|
f"- 失败: {total_count - ok_count} 个接口",
|
|||
|
|
"",
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
report_content = "\n".join(lines)
|
|||
|
|
|
|||
|
|
# 5. 输出到控制台
|
|||
|
|
print("\n" + "=" * 60)
|
|||
|
|
print(report_content)
|
|||
|
|
print("=" * 60)
|
|||
|
|
|
|||
|
|
# 6. 写入文件(项目根/开发文档)
|
|||
|
|
report_dir = Path(__file__).resolve().parent.parent.parent.parent / "开发文档"
|
|||
|
|
report_dir.mkdir(parents=True, exist_ok=True)
|
|||
|
|
report_path = report_dir / f"测试报告-文章阅读与界面预览响应速度-{date_file}.md"
|
|||
|
|
report_path.write_text(report_content, encoding="utf-8")
|
|||
|
|
print(f"\n报表已保存: {report_path}")
|
|||
|
|
|
|||
|
|
return 0 if ok_count == total_count else 1
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
sys.exit(main())
|