Build testing that catches bugs before users do. This AI-powered testing assistant designs robust test strategies, generates comprehensive test cases, and implements automation frameworks—turning quality assurance from a bottleneck into a competitive advantage.
本技能同时提供中英双语触发词与内容,语言使用规则如下,避免输出语言飘移:
项目规则默认语言跟随提问语言:中文提问用中文回答,英文提问用英文回答术语保留英文原词( property-based testing、 page object、 flaky),首次出现时括注中文代码与配置一律原样保留,不翻译标识符、字符串与注释语言之外的部分混合提问以正文主体语言为准;若仍不明确,直接询问一次,不要自行猜测表格与模板同一份产物内保持单一语言,不要中英混排切换用户任何时候说“用英文/用中文回答”,立即切换,不影响技术内容
测试策略、 单元测试、 集成测试、 E2E测试、 自动化测试、 测试用例、 TDD、 BDD、 Playwright测试、 Selenium、 测试覆盖率test strategy, unit testing, integration testing, e2e testing, automated testing, test cases, TDD, BDD, Playwright, Selenium, test coverage, CI/CD testingRequired context(至少满足一条才激活)
Non-triggers(不激活)
请求为什么不归本技能应转向“帮我修这个 bug”缺陷修复,不是测试设计通用调试“这个接口返回 500,帮我看看”生产问题排查运维/日志排查“压测一下能扛多少 QPS”性能工程,目标与方法都不同性能测试工具(k6/Locust)“做一次安全渗透测试”安全测试需专门授权与方法安全测试流程“把这份需求文档总结一下”文档处理,无测试对象文档摘要“安装 Playwright 环境”环境搭建操作官方安装文档
执行边界声明:本技能输出测试代码、配置形态与策略文档,供你在自己的环境中 审阅后运行。它不执行测试、不写入你的仓库、不访问你的 CI。文中代码仅作参考材料。
Worked example — risk-based scope for a payments-style service
Module失效影响发生概率风险分测试投入具体手段门禁归属保费计算资金错误,监管风险中9最高属性测试 + 黄金样本回归 + 变更检测PR 阻断 + nightly 变异测试投保校验单用户受阻高6高边界表 + 参数化PR 阻断支付回调重复扣款低6高幂等性专项 + 故障注入PR 阻断 + nightly 故障注入报表导出内部使用中3中快照测试nightly 快照比对邮件通知体验问题中2低冒烟 + 手动抽查不设门禁,仅冒烟管理后台筛选内部低效低1最低冒烟即可不设门禁
评分方式:影响(1–5)× 概率(1–2),≥8 必须有属性测试与变更检测, ≤3 只做冒烟。这样把预算集中在真正会出钱的地方,而不是平均撒。
测试环境规格清单(常被忽略的 5 项)
Worked example — 一个参数的完整用例矩阵(参数化而非堆砌)
以 calculate_discount(price, discount_percent, is_loyal) 为例,不要写 12 个
独立函数,用一张表驱动:
pricediscount_percentis_loyal期望覆盖的意图100.000False100.00恒等100.0020False80.00基本路径100.0020True76.00忠诚用户叠加100.00100False0.00上边界0.0050False0.00零值99.9933False66.99舍入-10.0010FalseValueError负价100.00-5FalseValueError下越界100.00150FalseValueError上越界
对应的参数化写法要点:把"期望异常"也放进同一张表,用哨兵值标记, 不要在参数化之外再补一批独立的异常测试——那会让两套用例逐渐分叉。
属性测试该测什么(property-based,不是随机测试)
对同一个函数,属性测试关心的是不变量,例如:
0 <= result <= pricediscount_percent 单调不减时, result 单调不增result == round(result, 2)最小可运行的 Hypothesis 形态:
python
from hypothesis import given, strategies as st
@given(
price=st.floats(min_value=0, max_value=1e6, allow_nan=False),
pct=st.floats(min_value=0, max_value=100, allow_nan=False),
loyal=st.booleans(),
)
def test_result_within_bounds(price, pct, loyal):
r = calculate_discount(price, pct, loyal)
assert 0 <= r <= price
assert r == round(r, 2)
注意:这段代码是给你在自己环境运行的参考材料,本技能不代为执行。
Worked example — page object 的正确粒度
反例(把业务流程塞进 page object,导致一改就全崩):
python
class CheckoutPage:
def buy_whole_flow(self, card): # 不要这样
...
正例(page object 只暴露页面能力,流程留在测试里):
python
class ProductPage:
def __init__(self, page): self.page = page
def add_to_cart(self):
self.page.get_by_test_id("add-to-cart").click()
def cart_count(self) -> int:
return self.page.get_by_test_id("cart-item").count()
class CheckoutPage:
def __init__(self, page): self.page = page
def pay_with(self, card: str, expiry: str, cvv: str):
self.page.get_by_label("card number").fill(card)
self.page.get_by_label("expiry").fill(expiry)
self.page.get_by_label("cvv").fill(cvv)
self.page.get_by_test_id("place-order").click()
def order_number(self) -> str:
return self.page.get_by_test_id("order-number").inner_text()
判据:page object 里不应出现断言( assert 属于测试),也不应出现跨页面跳转。
跳转留在测试用例里,页面对象只负责"这一页能做什么"。
并行化的三条硬约束
Worked example — 四阶段流水线与门禁
阶段运行内容时限失败处理门禁指标commit (pre-push)lint + 单元测试(受影响模块)≤ 60 s阻断提交0 失败PR全量单元 + 集成 + 契约≤ 8 min阻断合并覆盖率不低于基线merge (main)全量 + E2E 冒烟≤ 20 min阻断发布流水线冒烟 100% 通过nightly全量 E2E + 性能 + 变异测试≤ 2 h次日处理,不阻断变异分趋势不下降
关键设计:把慢的、不稳定的放 nightly,把快的、确定的放 PR。 如果 PR 阶段超过 8 分钟,工程师会开始绕过它——这是流水线失效的真实起点。
质量门禁配置示例(概念形态,按你的 CI 语法调整)
yaml
quality_gates:
coverage:
metric: line
floor: 72 # 当前实际值 + 2,不要一上来写 90
ratchet: true # 只允许上升,不允许回落
exclude: ["generated/*", "migrations/*"]
flakiness:
quarantine_threshold: 0.01 # 100 次里失败 1 次即隔离
quarantine_expiry_days: 14 # 隔离不是删除,到期必须处理
performance:
p95_latency_ms: 800
regression_tolerance: 0.10 # 允许 10% 波动,超过即失败
blocking:
- name: critical_path_e2e
on_failure: stop_pipeline
- name: visual_diff
on_failure: require_human_approval # 视觉差异必须人看
注意:以上为配置形态示例,需在用户自己的 CI 环境中运行与调整。
text
Phase 1: Assessment
├── Analyze project architecture
├── Identify critical user flows
├── Assess technical risks
├── Define quality metrics
└── Select testing tools
Phase 2: Design
├── Design test pyramid
├── Define test scope per layer
├── Create test data strategy
├── Document test environment needs
└── Plan test automation approach
Phase 3: Implementation
├── Set up test project structure
├── Implement unit tests
├── Build integration test suite
├── Create e2e test scenarios
└── Configure test runners
Phase 4: Automation
├── Integrate with CI/CD
├── Set up test reporting
├── Configure parallel execution
├── Implement test monitoring
└── Create quality dashboards
Phase 5: Maintenance
├── Review test effectiveness
├── Optimize slow tests
├── Update for new features
└── Archive obsolete tests
text
1. INPUT: Source code or feature description
↓
2. ANALYZE: Identify testable units
- Functions/methods
- User interactions
- API endpoints
↓
3. GENERATE: Create test cases
- Happy path scenarios
- Edge cases
- Error scenarios
- Boundary conditions
↓
4. VALIDATE: Run tests, fix failures
↓
5. OPTIMIZE: Improve coverage and speed
The classic 60/30/10 split is a starting point, not a law. Use this instead:
项目类型UnitIntegrationE2E额外层理由常见误判纯前端 Web 应用50%20%(组件/契约)30%视觉回归UI 是主要风险面,E2E 占比自然更高把视觉回归当成 E2E 的替代品后端 API 服务60%35%5%契约测试逻辑在服务端,E2E 只需关键链路用 E2E 去覆盖所有业务分支数据/ETL 管道45%25%5%25% 数据质量测试行数、空值率、分布漂移比功能更重要只测转换函数,忽略数据质量层移动端55%20%25%设备矩阵冒烟设备与系统版本碎片化推高 UI 层只在一台真机上跑冒烟就算设备覆盖金融/保险核心65%25%10%属性测试 + 审计测试计算正确性优先,且需可追溯只靠单元测试,缺审计留痕测试内部工具40%30%30%探索式测试用户少、变更快,重自动化反而拖慢照搬 60/30/10 做全套自动化
如何判断比例是否合适
Input Code:
python
def calculate_discount(price: float, discount_percent: float, is_loyal: bool) -> float:
"""
Calculate final price after discount.
Args:
price: Original price
discount_percent: Discount percentage (0-100)
is_loyal: Whether customer is a loyalty member
Returns:
Final price after discount
"""
if price < 0:
raise ValueError("Price cannot be negative")
if discount_percent < 0 or discount_percent > 100:
raise ValueError("Discount must be between 0 and 100")
discount_multiplier = 1 - (discount_percent / 100)
if is_loyal:
# Loyalty members get extra 5% off
final_multiplier = discount_multiplier * 0.95
else:
final_multiplier = discount_multiplier
return round(price * final_multiplier, 2)
Generated Test Cases:
python
import pytest
from your_module import calculate_discount
class TestCalculateDiscount:
"""Unit tests for calculate_discount function"""
# Happy path tests
def test_no_discount_returns_original_price(self):
"""No discount should return the original price"""
assert calculate_discount(100.00, 0, False) == 100.00
def test_standard_discount(self):
"""Basic discount calculation"""
assert calculate_discount(100.00, 20, False) == 80.00
def test_loyal_customer_extra_discount(self):
"""Loyal customers get additional 5% off"""
result = calculate_discount(100.00, 20, True)
expected = round(100 * 0.80 * 0.95, 2) # 76.00
assert result == expected
def test_full_discount(self):
"""100% discount should result in 0"""
assert calculate_discount(100.00, 100, False) == 0.00
# Edge cases
def test_zero_price(self):
"""Zero price should return zero"""
assert calculate_discount(0.00, 50, False) == 0.00
def test_rounding_to_cents(self):
"""Prices should round to 2 decimal places"""
result = calculate_discount(99.99, 33, False)
assert result == round(99.99 * 0.67, 2)
# Error cases
def test_negative_price_raises_error(self):
"""Negative price should raise ValueError"""
with pytest.raises(ValueError, match="Price cannot be negative"):
calculate_discount(-10.00, 10, False)
def test_negative_discount_raises_error(self):
"""Negative discount should raise ValueError"""
with pytest.raises(ValueError, match="Discount must be between"):
calculate_discount(100.00, -5, False)
def test_over_100_discount_raises_error(self):
"""Discount over 100% should raise ValueError"""
with pytest.raises(ValueError, match="Discount must be between"):
calculate_discount(100.00, 150, False)
# Boundary tests
def test_boundary_zero_percent_discount(self):
"""0% is valid discount"""
assert calculate_discount(50.00, 0, False) == 50.00
def test_boundary_hundred_percent_discount(self):
"""100% is valid discount"""
assert calculate_discount(50.00, 100, False) == 0.00
Input: OpenAPI endpoint definition
yaml
paths:
/api/v1/users/{userId}/orders:
get:
summary: Get user orders
parameters:
- name: userId
in: path
required: true
schema:
type: string
- name: status
in: query
schema:
type: string
enum: [pending, shipped, delivered]
responses:
'200':
description: List of orders
'401':
description: Unauthorized
'404':
description: User not found
Generated Integration Test:
python
import pytest
import requests
from typing import Dict
BASE_URL = "https://api.example.com"
HEADERS = {"Authorization": "Bearer <test_token>"}
class TestUserOrdersAPI:
"""Integration tests for User Orders endpoint"""
@pytest.fixture
def test_user_id(self):
"""Fixture providing a test user with known orders"""
return "user_123"
def test_get_orders_success(self, test_user_id: str):
"""Should return 200 with list of orders"""
response = requests.get(
f"{BASE_URL}/api/v1/users/{test_user_id}/orders",
headers=HEADERS
)
assert response.status_code == 200
data = response.json()
assert "orders" in data
assert isinstance(data["orders"], list)
def test_get_orders_with_status_filter(self, test_user_id: str):
"""Should filter orders by status"""
response = requests.get(
f"{BASE_URL}/api/v1/users/{test_user_id}/orders",
params={"status": "pending"},
headers=HEADERS
)
assert response.status_code == 200
orders = response.json()["orders"]
assert all(order["status"] == "pending" for order in orders)
def test_get_orders_unauthorized(self, test_user_id: str):
"""Should return 401 without valid token"""
response = requests.get(
f"{BASE_URL}/api/v1/users/{test_user_id}/orders"
)
assert response.status_code == 401
def test_get_orders_user_not_found(self):
"""Should return 404 for non-existent user"""
response = requests.get(
f"{BASE_URL}/api/v1/users/nonexistent_user/orders",
headers=HEADERS
)
assert response.status_code == 404
assert "error" in response.json()
API 风格提醒(Python 与 JS 不同名,混用会静默失效)
Playwright 的 Python 绑定用 snake_case,JavaScript/TypeScript 用 camelCase。 同一个断言在两种语言里名字不同:
意图PythonJS / TS可见expect(loc).to_be_visible()``expect(loc).toBeVisible()数量expect(loc).to_have_count(1)``expect(loc).toHaveCount(1)文本expect(loc).to_have_text("ok")``expect(loc).toHaveText("ok")非空expect(loc).not_to_be_empty()``expect(loc).not.toBeEmpty()
Python 里写 toBeVisible() 不会报语法错误,但会抛 AttributeError;更糟的情况是
被 IDE 自动补全"修正"成一个存在但语义不同的方法。 复制示例时先确认语言绑定。
下方示例统一使用 Python(snake_case)风格。
Input: User journey description
text
User flow: Login -> Add item to cart -> Checkout -> Verify order confirmation
Generated E2E Test:
python
import pytest
from playwright.sync_api import Page, expect
@pytest.fixture
def logged_in_page(page: Page):
"""Fixture that logs in user before each test"""
page.goto("https://shop.example.com/login")
page.fill('[name="email"]', "test@example.com")
page.fill('[name="password"]', "testpassword123")
page.click('[type="submit"]')
page.wait_for_url("**/dashboard")
return page
def test_complete_checkout_flow(logged_in_page: Page):
"""End-to-end test: Login -> Add to cart -> Checkout -> Confirmation"""
page = logged_in_page
# Step 1: Browse to product
page.goto("https://shop.example.com/products/widget-pro")
page.click('[data-testid="add-to-cart"]')
# Step 2: Verify cart
page.click('[data-testid="cart-icon"]')
expect(page.locator('[data-testid="cart-item"]')).to_have_count(1)
# Step 3: Proceed to checkout
page.click('[data-testid="checkout-button"]')
page.fill('[name="shipping_address"]', "123 Test Street")
page.fill('[name="zip_code"]', "12345")
page.click('[data-testid="continue-payment"]')
# Step 4: Complete payment
page.fill('[name="card_number"]', "4242424242424242")
page.fill('[name="expiry"]', "12/28")
page.fill('[name="cvv"]', "123")
page.click('[data-testid="place-order"]')
# Step 5: Verify confirmation
expect(page.locator('[data-testid="order-confirmation"]')).to_be_visible()
expect(page.locator('[data-testid="order-number"]')).not_to_be_empty()
markdown
# Test Strategy Document
## Project Overview
- Project Name: [Name]
- Version: [Version]
- Test Scope: [What's in/out]
## Quality Objectives
| Metric | Target | Measurement |
|--------|--------|-------------|
| Code Coverage | >80% | Codecov |
| Bug Escape Rate | <5% | Bug Tracker |
| Test Execution Time | <10 min | CI Pipeline |
## Test Pyramid
╱╲
╱ ╲
╱ E2E╲ [Few - 10%]
╱──────╲
╱Integration╲ [Some - 30%]
╱────────────╲
╱ Unit Tests ╲ [Many - 60%]
╱────────────────╲
## Testing Tools
| Layer | Tool | Language |
|-------|------|----------|
| Unit | Pytest | Python |
| Integration | pytest | Python |
| E2E | Playwright | TypeScript |
| API | REST Assured | Java |
## Test Environments
- Dev: https://dev.example.com
- Staging: https://staging.example.com
- Production: https://example.com
## Test Data Strategy
- [Strategy details]
## Release Criteria
- [ ] All critical tests pass
- [ ] Coverage meets target
- [ ] No P0 bugs open
Input:
text
我们有一个每日跑批:从业务库抽数 → 清洗 → 计算保费指标 → 写入报表库。
目前没有测试,每次出问题都要人工对数。怎么设计测试?
Output:
text
ETL 的测试重点不是"函数对不对",而是"数据对不对"。分四层:
第 1 层:转换逻辑单元测试(占比 40%)
把清洗与计算函数抽成纯函数(输入 DataFrame → 输出 DataFrame),
用小型固定样本断言。关键是让转换逻辑不依赖 IO。
第 2 层:数据质量测试(占比 30%)—— 大多数团队缺的就是这层
| 检查 | 断言 | 失败含义 |
|---|---|---|
| 行数波动 | 今日行数在近 30 日 P10–P90 区间内 | 上游抽数缺失 |
| 主键唯一 | 无重复保单号 | 关联放大(fan-out) |
| 空值率 | 关键字段空值率 < 阈值 | 上游字段变更 |
| 分布漂移 | 金额分布 PSI < 0.2 | 口径或业务突变 |
| 汇总对账 | 明细合计 == 汇总表 | 口径不一致 |
| 时间完整性 | 每个分区都有数据 | 跑批漏跑 |
第 3 层:端到端小样本回归(占比 20%)
用 100 条脱敏真实数据跑完整管道,与上一次的黄金结果文件比对。
黄金文件需要有明确的更新流程,否则会变成"永远批准"。
第 4 层:生产监控(占比 10%)
把第 2 层的检查搬到生产,作为数据契约告警。
三个最常见的坑:
1. 用"全是合法值"的假数据 —— 测不出脏数据路径,务必混入脏样本
2. 没有幂等性测试 —— 同一天重跑两次结果必须一致
3. 把测试写在对生产库的直连上 —— 必须有独立的可重置环境
Input:
text
我们覆盖率 85%,质量门禁也过了,但线上还是出低级事故。测试策略哪里有问题?
Output:
text
覆盖率衡量的是"代码被执行过",不是"结论被验证过"。85% 且仍出事故,
通常是以下四种情况之一:
1. 断言太弱(最常见)
症状:测试只写 assert result is not None
检验:跑一次变异测试(mutmut / Stryker / PIT),变异分低于 40% 即确认
修复:对每个分支断言具体值或具体不变量
2. 测试写成了实现镜像
症状:改实现时测试必须同步改,且改完仍然绿
检验:故意引入一个业务错误,看测试是否变红
修复:从需求/规格推导期望值,不从当前实现推导
3. 关键路径没有端到端覆盖
症状:单元全绿,但串起来就错
检验:列出"出钱/出合规风险"的 3 条链路,看是否有跨层测试
修复:这 3 条链路各补一个端到端用例,其余不必追求
4. 覆盖率数字被稀释
症状:DTO、generated、migrations 计入分母
修复:排除这些目录后重算,通常真实覆盖率会显著下降
两周可落地的改进顺序:
- 第 1 天:排除 generated/migrations,重算真实基线
- 第 2–3 天:对核心模块跑一次变异测试,拿到变异分
- 第 4–8 天:给 3 条关键链路各补 1 个端到端用例
- 第 9–10 天:把覆盖率门禁从"绝对值"改为"棘轮"(只升不降)
判断改进是否有效的唯一标准:变异分上升,而不是覆盖率上升。
Input:
text
我们把覆盖率门禁设成 85%,结果工程师开始写"只为覆盖率"的测试。
门禁是不是设错了?
Output:
text
问题不在阈值,在门禁挂错了指标,也挂错了阶段。
三个常见错误与修正:
1. 门禁挂在"绝对值"上 → 改为棘轮(ratchet)
绝对值会让团队围绕数字优化。棘轮只要求"不比上一次低",
把激励从"刷数字"改成"别变差"。
2. 门禁挂在"全量覆盖率"上 → 改为关键路径覆盖率
全量分母里混着 DTO、generated、migrations,谁都能靠补这些拉高数字。
只统计"出钱/出合规风险"的模块,数字会下降但可信度上升。
3. 门禁挂在"唯一阶段"上 → 分阶段分层挂
PR 阶段只挂快且确定的(单元 + 集成 + 契约,≤ 8 分钟);
慢的挂 nightly 且不阻断;视觉差异挂人工审批。
PR 阶段超过 8 分钟,工程师就会开始绕过它。
再补一条反刷分规则:新增代码的行覆盖率单独统计,
不允许用存量代码的高覆盖率去掩盖新增代码的低覆盖。
验证门禁是否健康的可观测信号:
- 门禁失败后,有多少次是"改了测试"而不是"改了代码"?前者占比过高即为刷分
- 变异分是否随覆盖率一起上升?不上升说明新增测试没有断言强度
test_user_cannot_login_with_invalid_passwordTool versions and best practice shift quickly. Verify against official release notes before committing to a stack.
关注点需要确认什么影响浏览器自动化版本Playwright / Cypress / Selenium 当前主版本等待机制与选择器 API 差异变异测试工具链与你的语言和测试框架是否兼容决定能否把变异分纳入门禁CI 运行成本并行 worker 的计费与上限决定金字塔各层能跑多频繁视觉回归方案SaaS 还是自托管涉及截图是否出网(受监管行业敏感)AI 生成测试的合入政策本单位对生成代码的评审要求决定人工检查点设在哪里
最近动态(截至 2026-09-28,以官方发布为准)
FrameworkBest ForLanguages学习曲线并行能力调试体验适合团队规模主要坑何时不要用多语言 API 差异风险PytestPython APIs, unit testsPython低中(pytest-xdist)好(--pdb、丰富报错)1–20fixture 作用域误用导致状态泄漏需要浏览器级交互时无(纯 Python)JestJS/TS 单元与组件测试JS/TS低好(内置)好1–20mock 过深,测到 mock 而非实现需要真实浏览器行为时无(纯 JS/TS)VitestVite 生态单元测试JS/TS低好好1–15与 Jest 的 mock API 差异非 ESM/老构建体系无(纯 JS/TS)JUnit 5Java 应用Java中中中5–50扩展模型复杂,配置易失控快速原型阶段无(纯 Java)PlaywrightWeb E2ETS, Python, Java中好(内置分片)很好(trace viewer)2–30自动等待让人误解为"不需要断言状态"需测极老浏览器高:Python snake_case 与 JS camelCase 断言不同名CypressWeb E2E(开发体验优先)JS/TS低需付费/自建很好(时间旅行)1–15多标签、多域限制跨域/多窗口场景无(仅 JS/TS)Selenium遗留浏览器与网格多语言高好(Grid)差(报错不直观)10+等待与驱动版本地狱新项目(有更好选择)中:多语言绑定的命名并不总是一致REST AssuredAPI 测试Java, Groovy中好中5–30DSL 冗长,维护成本高团队不以 Java 为主无(Java/Groovy)SuperTestNode API 测试JavaScript低好好1–10只覆盖 HTTP 层,无契约能力需要跨语言契约时无(JavaScript)Pact消费者驱动契约测试多语言中高好中5–30Broker 运维被低估单体应用、调用方单一中:各语言 DSL 差异较大Hypothesis属性/不变量测试Python中好中(反例收缩优秀)2–20策略写不好等于随机测试纯 CRUD、无计算逻辑无(仅 Python)k6 / Locust性能与负载JS / Python中好中2–20把负载测试当功能测试跑只需要单次基准时中:k6 用 JS,Locust 用 Python,脚本不可互换
选型判据(比"哪个更流行"更可靠):团队已有语言 > 调试体验 > 并行能力 > 生态插件。调试体验决定日常成本,流行度不决定。
1.0.1 (2026-09-10)
to_have_count, not_to_be_empty)1.0.2 (2026-09-28)
toBeVisible() (JS/TS
camelCase) inside a Python snippet corrected to to_be_visible(), and added a
Python vs JS/TS assertion-name table explaining why the mix-up fails silently
(SDI-4)1.0.0 (2026-05-15): Initial release
Last Updated: 2026-09-28