在多个页面中通过骨架屏优化加载状态。
在章节、礼物代付详情、阅读和搜索结果页面,用骨架屏替换传统加载指示器,以提升数据获取过程中的用户体验。 更新骨架屏样式,使加载状态更加美观。 实现章节和配置信息的缓存策略,以优化性能并减少冷启动问题。
This commit is contained in:
@@ -14,6 +14,18 @@
|
||||
|
||||
---
|
||||
|
||||
## 响应速度测试
|
||||
|
||||
`test_article_preview_speed.py`:文章阅读与界面预览 GET 接口响应速度测试。
|
||||
|
||||
```bash
|
||||
SOUL_TEST_ENV=soulapi python scripts/test/miniapp/test_article_preview_speed.py
|
||||
```
|
||||
|
||||
产出:控制台报表 + `开发文档/测试报告-文章阅读与界面预览响应速度-YYYYMMDD.md`
|
||||
|
||||
---
|
||||
|
||||
## 用例编写
|
||||
|
||||
在此目录下新增 `.md` 或测试脚本,按场景组织用例。
|
||||
|
||||
234
scripts/test/miniapp/test_article_preview_speed.py
Normal file
234
scripts/test/miniapp/test_article_preview_speed.py
Normal file
@@ -0,0 +1,234 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user