功能可用性自动化测试验证

0 阅读4分钟

功能可用性自动化测试验证

一、为什么需要“可用性”这一层测试

单元测试验证函数逻辑,集成测试验证模块协作,但线上故障往往出在两者之外:配置错误、依赖服务超时、证书过期、限流熔断。这类问题不会让任何单测变红,却会让功能整体不可用。可用性测试(Availability / Smoke Testing)要回答的只有三个问题:服务是否活着、核心链路是否通、失败后能否自动发现。

它必须满足三个工程属性:可重复、低成本、可集成到发布流程。下面给出一套可落地的实现。

二、分层设计

层级检测对象判定方式频率
L1 存活探测进程 / 端口TCP 或 /healthz 返回 20030s
L2 依赖探测DB、Redis、下游 API轻量读写、连通性1min
L3 核心链路登录→查询→下单等关键路径端到端断言业务字段每次发布 + 5min

分层的关键是快:L1/L2 单次执行控制在百毫秒级,L3 允许秒级但必须串行可重放。

三、探活与重试的实现

网络抖动不应直接判死,但重试必须有上限和退避,否则探测本身会放大故障。

import time
import requests

def probe(url: str, timeout: float = 3.0, retries: int = 3, backoff: float = 0.5) -> dict:
    """返回结构化探测结果,便于聚合统计与告警。"""
    last_err = None
    for i in range(retries):
        start = time.perf_counter()
        try:
            resp = requests.get(url, timeout=timeout)
            cost = (time.perf_counter() - start) * 1000
            if resp.status_code == 200 and resp.json().get("status") == "ok":
                return {"ok": True, "attempt": i + 1, "cost_ms": round(cost, 1)}
            last_err = f"HTTP {resp.status_code}"
        except Exception as e:
            last_err = repr(e)
        if i < retries - 1:
            time.sleep(backoff * (2 ** i))  # 指数退避
    return {"ok": False, "attempt": retries, "error": last_err}

注意两点:健康检查接口不要做重业务查询,否则探测会变成压测;返回体必须带语义字段,只判断 200 无法区分“进程活着但依赖全挂”。

四、依赖与核心链路的断言

依赖探测要“读一点、写一点、删掉”,避免污染数据。

def check_redis(conn, key="__probe__"):
    conn.set(key, "1", ex=5)
    assert conn.get(key) == b"1"
    conn.delete(key)

def check_core_flow(client):
    token = client.post("/api/login", json={"u": "probe", "p": "probe"}).json()["token"]
    headers = {"Authorization": f"Bearer {token}"}
    r = client.get("/api/orders?limit=1", headers=headers)
    assert r.status_code == 200
    body = r.json()
    assert "items" in body and body["total"] >= 0   # 断言业务字段,而非仅状态码

核心链路断言的常见误区是只判 status_code。业务可用性问题常表现为 200 但字段缺失、返回空列表、金额为 0。断言业务不变量(invariant)才有意义。

五、用 pytest 组织并接入 CI

import pytest, json

CASES = json.load(open("availability_cases.json"))  # 环境、URL、期望字段外置

@pytest.mark.availability
@pytest.mark.parametrize("case", CASES, ids=[c["name"] for c in CASES])
def test_availability(case):
    result = probe(case["url"])
    assert result["ok"], f"{case['name']} 不可用: {result.get('error')}"

配合 CI,把可用性测试作为发布流水线的门禁,失败即阻断发布并输出结构化报告:

- name: Availability Gate
  run: pytest -m availability --junitxml=report/availability.xml --timeout=60
- name: Notify
  if: failure()
  run: ./scripts/alert.sh "$(date -u +%FT%TZ) availability gate failed"

六、结果判定与告警策略

可用性测试的价值在噪声控制,建议遵守三条规则:

  1. 连续失败才告警(如连续 3 次),单次超时只记录不告警,避免抖动刷屏。
  2. 区分故障等级:L1 失败 = P0 立即告警;L3 失败 = P1 工单;L2 单依赖失败 = P2 日报聚合。
  3. 记录趋势而非只看当次:采集 cost_ms 与失败率,做周维度对比,性能劣化往往先于故障出现。
def judge(history, consecutive=3):
    tail = history[-consecutive:]
    if len(tail) == consecutive and all(not h["ok"] for h in tail):
        return "P0", "连续失败,触发告警"
    if history and not history[-1]["ok"]:
        return "P1", "单次失败,记录待观察"
    return "OK", "正常"

七、落地清单

  • 健康检查接口与业务接口分离,禁止重查询;
  • 探测数据可回滚、可清理,不侵入生产数据;
  • 所有断言落到业务字段,不只判 HTTP 状态码;
  • 外置用例配置,多环境同一套脚本;
  • 接入 CI 门禁与定时调度,失败分等级告警;
  • 留存执行耗时与失败率,用于趋势分析。

可用性测试不追求覆盖率,追求的是“故障发生后,系统比用户先知道”。把上面几层跑稳,发布流程就具备了最基本的自证能力。

海量精选技术文档和实战案例持续更新,敬请关注【风骏时光少年】