在章节、礼物代付详情、阅读和搜索结果页面,用骨架屏替换传统加载指示器,以提升数据获取过程中的用户体验。 更新骨架屏样式,使加载状态更加美观。 实现章节和配置信息的缓存策略,以优化性能并减少冷启动问题。
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())
|