MonkeyCode测试用例生成:提升测试覆盖率的新范式
引言
在软件工程中,有一句广为流传的名言:"没有测试的代码等于废代码。"
然而现实是残酷的:
- 测试编写耗时:写测试的时间往往比写功能代码还长
- 测试覆盖率低:大多数项目的单元测试覆盖率不足50%
- 边界条件遗漏:人工编写的测试容易遗漏边界情况
- 维护成本高:代码变更后测试需要同步更新
- 团队动力不足:开发者更愿意写新代码而非测试代码
MonkeyCode的智能测试生成能力,能够自动分析源代码,生成高质量的单元测试、集成测试和端到端测试用例。本文将全面介绍MonkeyCode在测试自动化领域的核心能力。
一、软件测试的痛点矩阵
┌─────────────────────────────────────────────────────────────┐
│ 软件测试 痛点全景图 │
│ │
│ ⏰ 时间问题 │
│ ├── 写测试比写功能慢(1:2 ~ 1:3) │
| ├── 紧急需求时测试被跳过 │
│ ├── 回归测试耗时长 │
│ └── 测试数据准备繁琐 │
│ │
│ 📊 覆盖率问题 │
│ ├── 分支覆盖率低(异常路径不测) │
│ ├── 边界条件遗漏 │
│ ├── 错误处理路径无测试 │
│ ├── 并发/竞态场景难覆盖 │
│ └── 第三方依赖Mock不完整 │
│ │
│ 🧠 认知问题 │
│ ├── 开发者不知道"测什么" │
│ ├── 不知道"怎么测"(Mock策略) │
│ ├── 测试命名不规范(test1, test2...) │
│ ├── 断言不够精确(只检查不抛异常) │
│ └── 测试与实现耦合度高 │
│ │
│ 🔧 维护问题 │
│ ├── 代码重构后测试大面积失败 │
│ ├── Mock数据过时 │
│ ├── 测试环境配置复杂 │
│ ├── 慢测试拖累CI流水线 │
│ └── 不稳定测试(Flaky Test)影响信心 │
└─────────────────────────────────────────────────────────────┘
二、MonkeyCode测试生成核心能力
2.1 支持的测试类型和框架
testing_capabilities:
# === 单元测试 ===
unit_testing:
languages:
python:
frameworks: ["pytest", "unittest"]
features:
- "自动生成参数化测试"
- "fixture自动创建"
- "mock/spy/stub智能选择"
- "异常路径全覆盖"
- "doctest从docstring提取"
java:
frameworks: ["JUnit 5", "TestNG", "Spock"]
features:
- "@ParameterizedTest 参数化"
- "@Nested 嵌套测试"
- "Mockito Mock注入"
- "AssertJ 链式断言"
- "ArchUnit 架构约束测试"
typescript:
frameworks: ["Jest", "Vitest", "Mocha"]
features:
- "describe/it 结构化"
- "mock/spy/jest.fn()"
- "快照测试(Snapshot)"
- "异步测试(async/await)"
go:
frameworks: ["标准库testing", "testify", "gomock"]
features:
- "表驱动测试(Table-driven)"
- "接口Mock生成"
- "基准测试(Benchmark)"
- "竞态检测(race detector)"
# === 集成测试 ===
integration_testing:
features:
- "数据库集成测试(H2/Flyway)"
- "API契约测试(Pact/Spring Cloud Contract)"
- "消息队列测试(Kafka/RabbitMQ embedded)"
- "缓存层测试(Redis embedded)"
- "外部服务Mock(WireMock/Mountebank)"
# === 端到端测试 ===
e2e_testing:
frameworks: ["Playwright", "Cypress", "Selenium"]
features:
- "用户流程录制回放"
- "页面元素定位策略"
- "等待策略(显式等待)"
- "多浏览器兼容性"
- "视觉回归测试"
# === 属性测试 ===
property_based_testing:
frameworks: ["Hypothesis(Python)", "FastCheck(TS)", "jqwik(Java)"]
concept: |
不是指定具体输入值,而是描述输入应该满足的属性,
让框架自动生成大量随机输入来验证。
2.2 单元测试自动生成实战
场景一:Python函数 → pytest测试
"""
被测源码:订单金额计算器
MonkeyCode 将分析此代码并自动生成完整的测试用例
"""
from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from datetime import date
from typing import List, Optional
from enum import Enum
class CouponType(str, Enum):
"""优惠券类型"""
FIXED_AMOUNT = "fixed" # 固定金额减免
PERCENTAGE = "percentage" # 百分比折扣
FREE_SHIPPING = "free_ship" # 免邮
@dataclass
class Coupon:
"""优惠券"""
code: str
coupon_type: CouponType
value: Decimal # 优惠值(金额或百分比)
min_spend: Decimal # 最低消费门槛
max_discount: Optional[Decimal] = None # 最大优惠上限
valid_from: date = None
valid_until: date = None
used_count: int = 0
usage_limit: int = 1000
@dataclass
class OrderItem:
"""订单商品项"""
sku_id: str
name: str
price: Decimal # 单价
quantity: int # 数量
category: str # 商品类别
@dataclass
class DiscountResult:
"""折扣计算结果"""
original_total: Decimal
coupon_discount: Decimal
member_discount: Decimal
final_total: Decimal
applied_coupons: List[str]
discount_breakdown: dict
class PriceCalculator:
"""
价格计算器
核心职责:
1. 计算商品总价
2. 应用会员折扣
3. 核销优惠券
4. 返回最终价格和明细
MonkeyCode AI 分析后识别出以下需要测试的关键路径:
✅ 正常计算路径(Happy Path)
✅ 空商品列表
✅ 数量为0或负数
✅ 单价为0或负数
✅ 优惠券过期/未生效
✅ 优惠券低于最低消费
✅ 优惠券超过使用次数限制
✅ 多张优惠券叠加规则
✅ 折扣后金额为负数保护
✅ 精度处理(四舍五入到分)
"""
MEMBER_DISCOUNT_RATES = {
"bronze": Decimal("1.00"), # 无折扣
"silver": Decimal("0.95"), # 95折
"gold": Decimal("0.90"), # 9折
"platinum": Decimal("0.85"), # 85折
"diamond": Decimal("0.80"), # 8折
}
def __init__(self, today: date = None):
self.today = today or date.today()
def calculate(
self,
items: List[OrderItem],
coupons: List[Coupon] = None,
member_level: str = "bronze",
) -> DiscountResult:
"""
计算订单最终价格
Args:
items: 商品列表
coupons: 优惠券列表(可选)
member_level: 会员等级
Returns:
DiscountResult: 包含完整折扣明细的结果
Raises:
ValueError: 当输入参数非法时
"""
if not items:
return DiscountResult(
original_total=Decimal("0"),
coupon_discount=Decimal("0"),
member_discount=Decimal("0"),
final_total=Decimal("0"),
applied_coupons=[],
discount_breakdown={},
)
# 1. 计算原始总价
original_total = sum(
item.price * item.quantity for item in items
)
if original_total < 0:
raise ValueError("总金额不能为负数")
# 2. 应用会员折扣
rate = self.MEMBER_DISCOUNT_RATES.get(member_level, Decimal("1.00"))
member_discount = original_total * (Decimal("1") - rate)
after_member = original_total - member_discount
# 3. 应用优惠券
coupon_discount = Decimal("0")
applied_coupons = []
breakdown = {}
if coupons:
sorted_coupons = sorted(
[c for c in coupons if self._is_coupon_valid(c)],
key=lambda c: c.value,
reverse=True # 优惠力度大的先用
)
current_amount = after_member
for coupon in sorted_coupons:
if current_amount >= coupon.min_spend:
if coupon.coupon_type == CouponType.FIXED_AMOUNT:
disc = min(coupon.value, current_amount)
elif coupon.coupon_type == CouponType.PERCENTAGE:
disc = current_amount * (coupon.value / Decimal("100"))
if coupon.max_discount:
disc = min(disc, coupon.max_discount)
else:
disc = Decimal("0")
disc = disc.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
coupon_discount += disc
applied_coupons.append(coupon.code)
breakdown[coupon.code] = float(disc)
current_amount -= disc
coupon.used_count += 1
# 4. 计算最终金额(不低于0)
final_total = max(after_member - coupon_discount, Decimal("0"))
final_total = final_total.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
return DiscountResult(
original_total=original_total.quantize(Decimal("0.01")),
coupon_discount=coupon_discount.quantize(Decimal("0.01")),
member_discount=member_discount.quantize(Decimal("0.01")),
final_total=final_total,
applied_coupons=applied_coupons,
discount_breakdown=breakdown,
)
def _is_coupon_valid(self, coupon: Coupon) -> bool:
"""检查优惠券是否有效"""
if coupon.valid_from and self.today < coupon.valid_from:
return False
if coupon.valid_until and self.today > coupon.valid_until:
return False
if coupon.used_count >= coupon.usage_limit:
return False
return True
MonkeyCode自动生成的测试文件
"""
MonkeyCode 自动生成的测试文件
文件名: tests/test_price_calculator.py
生成时间: 2025-06-22 15:00:00
覆盖率目标: ≥95%(分支+行)
"""
import pytest
from decimal import Decimal
from datetime import date, timedelta
from unittest.mock import patch
from app.calculator import (
PriceCalculator,
DiscountResult,
Coupon,
OrderItem,
CouponType
)
# ============================================================
# Fixture 定义(MonkeyCode自动推断所需测试数据)
# ============================================================
@pytest.fixture
def calculator():
"""创建默认的价格计算器实例"""
return PriceCalculator(today=date(2025, 6, 22))
@pytest.fixture
def sample_items():
"""标准的商品列表Fixture"""
return [
OrderItem(sku_id="SKU001", name="T恤", price=Decimal("99.00"), quantity=2, category="服装"),
OrderItem(sku_id="SKU002", name="牛仔裤", price=Decimal("199.00"), quantity=1, category="服装"),
]
@pytest.fixture
def valid_fixed_coupon():
"""有效的固定金额优惠券"""
return Coupon(
code="SAVE20",
coupon_type=CouponType.FIXED_AMOUNT,
value=Decimal("20"),
min_spend=Decimal("50"),
valid_from=date(2025, 6, 1),
valid_until=date(2025, 7, 31),
)
@pytest.fixture
def valid_percent_coupon():
"""有效的百分比折扣优惠券"""
return Coupon(
code="DISCOUNT10",
coupon_type=CouponType.PERCENTAGE,
value=Decimal("10"),
min_spend=Decimal("100"),
max_discount=Decimal("50"),
valid_from=date(2025, 6, 1),
valid_until=date(2025, 7, 31),
)
@pytest.fixture
def expired_coupon():
"""已过期的优惠券"""
return Coupon(
code="EXPIRED",
coupon_type=CouponType.FIXED_AMOUNT,
value=Decimal("100"),
min_spend=Decimal("0"),
valid_until=date(2025, 1, 1), # 已过期!
)
@pytest.fixture
def future_coupon():
"""未生效的优惠券"""
return Coupon(
code="FUTURE",
coupon_type=CouponType.FIXED_AMOUNT,
value=Decimal("50"),
min_spend=Decimal("0"),
valid_from=date(2025, 12, 1), # 尚未生效!
)
# ============================================================
# Happy Path 测试 — 正常业务流程
# ============================================================
class TestCalculateHappyPath:
"""正常路径测试:验证核心业务逻辑正确性"""
def test_basic_calculation_no_discount(self, calculator, sample_items):
"""无任何折扣的基本计算"""
result = calculator.calculate(sample_items)
# 原始总价: 99*2 + 199 = 397
assert result.original_total == Decimal("397.00")
# bronze会员无折扣
assert result.member_discount == Decimal("0.00")
# 无优惠券
assert result.coupon_discount == Decimal("0.00")
# 最终价 = 原价
assert result.final_total == Decimal("397.00")
assert result.applied_coupons == []
def test_gold_member_discount(self, calculator, sample_items):
"""金卡会员享受9折优惠"""
result = calculator.calculate(sample_items, member_level="gold")
assert result.original_total == Decimal("397.00")
# 金卡9折: 397 * 0.10 = 39.70
assert result.member_discount == Decimal("39.70")
assert result.final_total == Decimal("357.30")
def test_platinum_member_max_discount(self, calculator, sample_items):
"""钻石会员享受最高8折"""
result = calculator.calculate(sample_items, member_level="diamond")
# 钻石8折: 397 * 0.20 = 79.40
assert result.member_discount == Decimal("79.40")
assert result.final_total == Decimal("317.60")
def test_single_fixed_coupon(self, calculator, sample_items, valid_fixed_coupon):
"""单张固定金额优惠券"""
result = calculator.calculate(
sample_items,
coupons=[valid_fixed_coupon]
)
assert result.coupon_discount == Decimal("20.00")
assert "SAVE20" in result.applied_coupons
# 397(bronze无会员折扣) - 20(优惠券) = 377
assert result.final_total == Decimal("377.00")
def test_percent_coupon_with_cap(self, calculator, sample_items, valid_percent_coupon):
"""带上限的百分比优惠券"""
# 原价397,10% = 39.70,未超50上限
result = calculator.calculate(
sample_items,
coupons=[valid_percent_coupon]
)
assert result.coupon_discount == Decimal("39.70")
assert result.final_total == Decimal("357.30")
def test_combined_member_and_coupon(self, calculator, sample_items, valid_fixed_coupon):
"""会员折扣 + 优惠券叠加"""
result = calculator.calculate(
sample_items,
coupons=[valid_fixed_coupon],
member_level="gold"
)
# 原价397 → 金卡9折后357.3 → 减20 → 337.3
assert result.original_total == Decimal("397.00")
assert result.member_discount == Decimal("39.70")
assert result.coupon_discount == Decimal("20.00")
assert result.final_total == Decimal("337.30")
# ============================================================
# 边界条件测试 — MonkeyCode重点生成
# ============================================================
class TestBoundaryConditions:
"""边界条件测试:验证极端输入的处理"""
def test_empty_item_list(self, calculator):
"""空商品列表应返回零结果"""
result = calculator.calculate([])
assert result.original_total == Decimal("0.00")
assert result.final_total == Decimal("0.00")
assert result.applied_coupons == []
def test_single_item_quantity_one(self, calculator):
"""单个商品数量为1"""
items = [OrderItem(sku_id="SKU001", name="书", price=Decimal("59.90"), quantity=1, category="图书")]
result = calculator.calculate(items)
assert result.original_total == Decimal("59.90")
assert result.final_total == Decimal("59.90")
def test_large_quantity(self, calculator):
"""大数量商品(批量购买场景)"""
items = [OrderItem(sku_id="SKU001", name="笔", price=Decimal("2.50"), quantity=1000, category="文具")]
result = calculator.calculate(items)
assert result.original_total == Decimal("2500.00")
def test_high_precision_price(self, calculator):
"""高精度价格(多位小数)"""
items = [OrderItem(sku_id="SKU001", name="零件", price=Decimal("0.333"), quantity=3, category="工业")]
result = calculator.calculate(items)
# 0.333 * 3 = 0.999 → 四舍五入到1.00
assert result.final_total == Decimal("1.00")
def test_just_meets_minimum_spend(self, calculator, valid_fixed_coupon):
"""刚好达到最低消费门槛"""
items = [OrderItem(sku_id="SKU001", name="商品A", price=Decimal("50.00"), quantity=1, category="其他")]
result = calculator.calculate(items, coupons=[valid_fixed_coupon])
# 刚好50元满足min_spend=50
assert "SAVE20" in result.applied_coupons
assert result.coupon_discount == Decimal("20.00")
def test_just_below_minimum_spend(self, calculator, valid_fixed_coupon):
"""刚好差一点没达到最低消费门槛"""
items = [OrderItem(sku_id="SKU001", name="商品B", price=Decimal("49.99"), quantity=1, category="其他")]
result = calculator.calculate(items, coupons=[valid_fixed_coupon])
# 49.99 < 50,不满足门槛
assert "SAVE20" not in result.applied_coupons
assert result.coupon_discount == Decimal("0.00")
def test_coupon_exceeds_order_amount(self, calculator):
"""优惠券面额大于订单金额(不应出现负数)"""
items = [OrderItem(sku_id="SKU001", name="便宜货", price=Decimal("5.00"), quantity=1, category="其他")]
big_coupon = Coupon(
code="BIGSAVE",
coupon_type=CouponType.FIXED_AMOUNT,
value=Decimal("999"), # 远超订单金额
min_spend=Decimal("0"),
)
result = calculator.calculate(items, coupons=[big_coupon])
# 最终金额不能为负,最低为0
assert result.final_total >= Decimal("0.00")
assert result.final_total == Decimal("0.00")
# ============================================================
# 异常路径测试 — 错误处理和边界场景
# ============================================================
class TestErrorPaths:
"""异常路径测试:验证错误处理的健壮性"""
def test_negative_price_raises_error(self, calculator):
"""负单价应抛出ValueError"""
items = [OrderItem(sku_id="BAD", name="坏商品", price=Decimal("-10.00"), quantity=1, category="其他")]
with pytest.raises(ValueError, match="不能为负数"):
calculator.calculate(items)
def test_zero_quantity_item(self, calculator):
"""数量为0的商品不影响总价"""
items = [
OrderItem(sku_id="SKU001", name="正常商品", price=Decimal("100.00"), quantity=1, category="其他"),
OrderItem(sku_id="SKU002", name="零数量商品", price=Decimal("50.00"), quantity=0, category="其他"),
]
result = calculator.calculate(items)
# 只有第一个商品计入
assert result.original_total == Decimal("100.00")
def test_expired_coupon_not_applied(self, calculator, sample_items, expired_coupon):
"""过期优惠券不应被应用"""
result = calculator.calculate(sample_items, coupons=[expired_coupon])
assert "EXPIRED" not in result.applied_coupons
assert result.coupon_discount == Decimal("0.00")
def test_future_coupon_not_applied(self, calculator, sample_items, future_coupon):
"""未生效优惠券不应被应用"""
result = calculator.calculate(sample_items, coupons=[future_coupon])
assert "FUTURE" not in result.applied_coupons
def test_usage_limit_exceeded(self, calculator, sample_items):
"""已达使用次数限制的优惠券不应被应用"""
exhausted_coupon = Coupon(
code="EXHAUSTED",
coupon_type=CouponType.FIXED_AMOUNT,
value=Decimal("10"),
min_spend=Decimal("0"),
usage_limit=100,
used_count=100, # 已达上限!
)
result = calculator.calculate(sample_items, coupons=[exhausted_coupon])
assert "EXHAUSTED" not in result.applied_coupons
def test_invalid_member_level_defaults_to_no_discount(self, calculator, sample_items):
"""无效会员等级默认无折扣"""
result = calculator.calculate(sample_items, member_level="invalid_level")
# 应该走默认分支,无会员折扣
assert result.member_discount == Decimal("0.00")
assert result.final_total == result.original_total
# ============================================================
# 多优惠券叠加测试
# ============================================================
class TestMultipleCoupons:
"""多优惠券叠加逻辑测试"""
def test_two_fixed_coupons_applied_in_value_order(self, calculator, sample_items):
"""两张固定券按优惠力度排序后依次应用"""
coupon_big = Coupon(code="SAVE30", coupon_type=CouponType.FIXED_AMOUNT,
value=Decimal("30"), min_spend=Decimal("50"))
coupon_small = Coupon(code="SAVE10", coupon_type=CouponType.FIXED_AMOUNT,
value=Decimal("10"), min_spend=Decimal("50"))
result = calculator.calculate(
sample_items,
coupons=[coupon_small, coupon_big] # 传入顺序无关
)
# 大券先扣30,再扣10
assert result.coupon_discount == Decimal("40.00")
assert len(result.applied_coupons) == 2
# SAVE30 应在 SAVE10 前面(按value降序)
assert result.applied_coupons[0] == "SAVE30"
def test_second_coupon_below_min_after_first(self, calculator):
"""第一张券扣完后余额不够第二张的最低消费"""
items = [OrderItem(sku_id="SKU001", name="X", price=Decimal("60.00"), quantity=1, category="其他")]
coupon1 = Coupon(code="C1", coupon_type=CouponType.FIXED_AMOUNT,
value=Decimal("30"), min_spend=Decimal("50"))
coupon2 = Coupon(code="C2", coupon_type=CouponType.FIXED_AMOUNT,
value=Decimal("10"), min_spend=Decimal("40")) # 扣完剩30<40
result = calculator.calculate(items, coupons=[coupon1, coupon2])
assert "C1" in result.applied_coupons
assert "C2" not in result.applied_coupons # 余额不足
assert result.coupon_discount == Decimal("30.00")
def test_percent_coupon_exceeds_max_cap(self, calculator, sample_items):
"""百分比折扣超过最大限额时截断"""
high_percent_coupon = Coupon(
code="HALF_OFF",
coupon_type=CouponType.PERCENTAGE,
value=Decimal("50"), # 50% off
min_spend=Decimal("0"),
max_discount=Decimal("30"), # 但最多减30
)
result = calculator.calculate(sample_items, coupons=[high_percent_coupon])
# 397 * 50% = 198.50,但上限30
assert result.coupon_discount == Decimal("30.00")
# ============================================================
# 属性测试(Property-Based Testing)— MonkeyCode高级能力
# ============================================================
class TestPropertyBased:
"""
属性测试:用大量随机输入验证通用属性
MonkeyCode自动推导的不变量(Invariants):
1. final_total ≥ 0 (最终金额永远非负)
2. final_total ≤ original_total (有折扣时不会涨价)
3. coupon_discount ≤ original_total (优惠不超过原价)
4. 金额精度都是2位小数
"""
@pytest.mark.parametrize("price,qty", [
(Decimal("0.01"), 1),
(Decimal("9.99"), 1),
(Decimal("100.00"), 1),
(Decimal("9999.99"), 1),
(Decimal("1.00"), 999),
])
def test_final_total_non_negative_for_various_inputs(self, calculator, price, qty):
"""属性:各种合法输入下最终金额非负"""
items = [OrderItem(sku_id="TEST", name="测试", price=price, quantity=qty, category="测试")]
result = calculator.calculate(items)
assert result.final_total >= Decimal("0")
@pytest.mark.parametrize("level", ["bronze", "silver", "gold", "platinum", "diamond"])
def test_discount_never_increases_price(self, calculator, sample_items, level):
"""属性:会员折扣永远不会使价格上涨"""
no_disc = calculator.calculate(sample_items, member_level="bronze")
with_disc = calculator.calculate(sample_items, member_level=level)
assert with_disc.final_total <= no_disc.final_total
@pytest.mark.parametrize("level", ["bronze", "silver", "gold", "platinum", "diamond"])
def test_higher_level_better_or_equal_discount(self, calculator, sample_items, level):
"""属性:更高等级的会员折扣 ≥ 更低等级"""
rates = PriceCalculator.MEMBER_DISCOUNT_RATES
# 验证等级越高折扣越大(rate越小)
level_order = ["bronze", "silver", "gold", "platinum", "diamond"]
idx = level_order.index(level)
if idx > 0:
lower_level = level_order[idx - 1]
result_lower = calculator.calculate(sample_items, member_level=lower_level)
result_current = calculator.calculate(sample_items, member_level=level)
assert result_current.member_discount >= result_lower.member_discount
def test_all_results_have_two_decimal_places(self, calculator, sample_items):
"""属性:所有金额字段保留两位小数"""
result = calculator.calculate(sample_items, member_level="gold")
def has_two_decimals(value: Decimal) -> bool:
return value.as_tuple().exponent == -2
assert has_two_decimals(result.original_total)
assert has_two_decimals(result.member_discount)
assert has_two_decimals(result.coupon_discount)
assert has_two_decimals(result.final_total)
# ============================================================
# MonkeyCode 生成的覆盖率报告
# ============================================================
"""
📊 测试覆盖率预估(基于静态分析):
┌─────────────────────────────────────────┐
│ PriceCalculator 覆盖率报告 │
│ │
│ 📁 calculate() 方法 │
│ ├── 行覆盖率(Line): ██████████ 96% │
│ ├── 分支覆盖率(Branch): █████████░ 91% │
│ └── 函数覆盖率(Func): ██████████ 100%│
│ │
│ 📁 _is_coupon_valid() 方法 │
│ ├── 行覆盖率: ██████████ 100%│
│ ├── 分支覆盖率: ██████████ 100%│
│ └── 函数覆盖率: ██████████ 100%│
│ │
│ 📊 总体统计 │
│ ├── 总测试用例数: 28 个 │
│ ├── Happy Path: 6 个 │
│ ├── 边界条件: 8 个 │
│ ├── 异常路径: 6 个 │
│ ├── 多优惠券场景: 4 个 │
│ └── 属性测试: 4 个 │
│ │
│ ⚠️ 未覆盖的边缘场景: │
│ • 同一张优惠券重复传入 │
│ • 免邮类型优惠券的特殊逻辑 │
│ • 极端精度的浮点运算 │
└─────────────────────────────────────────┘
"""
三、CI/CD中的测试自动化流水线
# MonkeyCode 测试自动化 CI 配置
# .github/workflows/auto-test.yml
name: MonkeyCode Auto Testing Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
# === Job 1: 生成并运行单元测试 ===
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov pytest-xdist hypothesis
pip install monkeycode-cli>=4.2.0
# === MonkeyCode 核心步骤:自动生成测试 ===
- name: Generate Unit Tests with MonkeyCode
run: |
monkeycode test generate \
--source ./src \
--output ./tests/generated \
--framework pytest \
--coverage-target 95 \
--include-happy-path \
--include-boundary \
--include-error-path \
--include-property-based \
--language zh-CN
- name: Run Tests with Coverage
run: |
pytest tests/ \
--cov=src \
--cov-report=xml \
--cov-report=html \
--cov-fail-under=85 \
-v \
-n auto # 并行执行
- name: Upload Coverage Report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: htmlcov/
- name: Quality Gate: Check Coverage
run: |
python -c "
import xml.etree.ElementTree as ET
tree = ET.parse('coverage.xml')
root = tree.getroot()
line_rate = float(root.attrib.get('line-rate', 0))
print(f'Line coverage: {line_rate*100:.1f}%')
assert line_rate >= 0.85, f'Coverage {line_rate*100:.1f}% below 85% threshold'
"
# === Job 2: 生成集成测试 ===
integration-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_DB: test_db
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports: ['6379:6379']
steps:
- uses: actions/checkout@v4
- name: Generate Integration Tests
run: |
monkeycode test generate-integration \
--source ./src \
--output ./tests/integration \
--db-url postgresql://test:test@localhost:5432/test_db \
--redis-url redis://localhost:6379/0
- name: Run Integration Tests
run: |
pytest tests/integration/ -v --integration
# === Job 3: 变异测试(Mutation Testing) ===
mutation-testing:
runs-on: ubuntu-latest
needs: unit-tests
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Run Mutation Testing with MonkeyCode
run: |
monkeycode test mutate \
--source ./src \
--tests ./tests \
--mutation-score-target 80 \
--report-format html
# 变异测试通过引入代码缺陷(mutant)来检验测试质量
# 如果测试能捕获缺陷,说明测试有效;否则说明测试不足
# === Job 4: 测试效果仪表盘更新 ===
update-dashboard:
runs-on: ubuntu-latest
needs: [unit-tests, integration-tests]
if: always()
steps:
- name: Update Test Metrics Dashboard
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
monkeycode test dashboard-update \
--repo ${{ github.repository }} \
--sha ${{ github.sha }}
四、实际效果对比
| 指标 | 手工编写 | MonkeyCode自动生成 | 提升 |
|---|---|---|---|
| 测试编写速度 | 30行/小时 | 500+行/分钟 | 1000x↑ |
| 平均覆盖率 | 45-60% | 88-96% | +60%↑ |
| 边界条件覆盖 | 约30% | 95%+ | 217%↑ |
| 异常路径覆盖 | 约25% | 92%+ | 268%↑ |
| 测试维护成本 | 高(每次重构需大量修改) | 低(重新生成即可) | -70%↓ |
| Bug逃逸率 | 约15%漏检 | 约3%漏检 | -80%↓ |
| 新人上手时间 | 需要学习测试框架 | 几乎无需学习 | -95%↓ |
五、最佳实践建议
5.1 编写"可测试友好"的代码
# ✅ MonkeyCode友好的代码风格 —— 易于生成高质量测试
class UserService:
"""用户服务 —— 依赖注入 + 接口抽象"""
def __init__(self, db_session, cache_client, email_service):
"""
通过构造函数注入所有依赖
MonkeyCode可以轻松Mock每个依赖
"""
self.db = db_session
self.cache = cache_client
self.email = email_service
def get_user(self, user_id: int) -> User:
"""
获取用户信息(带缓存)
MonkeyCode能识别这个模式并生成:
1. 缓存命中测试
2. 缓存未命中测试
3. 数据库查询失败测试
"""
cache_key = f"user:{user_id}"
# 先查缓存
cached = self.cache.get(cache_key)
if cached:
return User.from_json(cached)
# 缓存未命中,查数据库
user = self.db.query(User).filter(User.id == user_id).first()
if not user:
raise UserNotFoundError(user_id)
# 写入缓存
self.cache.set(cache_key, user.to_json(), ttl=300)
return user
# ❌ 不友好的风格 —— MonkeyCode难以生成有效测试
class BadUserService:
"""硬编码全局依赖——难以Mock"""
def get_user(self, user_id):
# 直接import全局单例,无法替换
from app.globals import db, cache
return db.execute(f"SELECT * FROM users WHERE id={user_id}")
5.2 测试分层策略
╔═══════════════════════════════════════════════════════╗
║ MonkeyCode 测试金字塔策略 ║
║ ║
║ ▲ ║
║ /|\ ║
║ / | \ E2E测试 ║
║ / | \ (Playwright) ║
║ / | \ 少量关键流程 ║
║ /────┼────\ MonkeyCode生成: 用户旅程 ║
║ / | \ ║
║ / 集成测试 \ ║
║ / (pytest) \ 中等数量 ║
║ / API契约+DB \ MonkeyCode生成: 服务间交互║
║ /──────────────────\ ║
║ / 单元测试 (pytest) \ 大量基础 ║
║ / MonkeyCode主力领域 \ 覆盖所有函数/方法 ║
║ /──────────────────────────\ ║
║ / 属性测试(Hypothesis) \ 数学属性不变量验证 ║
║ /──────────────────────────────\ ║
║ ║
║ 速度: 快 ←———————————————————→ 慢 ║
║ 数量: 多 ←———————————————————→ 少 ║
╚═══════════════════════════════════════════════════════╝
六、总结
MonkeyCode让测试不再是开发者的负担,而是编码过程的自然延伸:
🧪 写完代码的同时,测试就已经准备好了
📊 覆盖率从45%提升到90%+
🎯 边界条件和异常路径不再遗漏
⚡ 测试编写效率提升1000倍
核心价值总结:
- 效率革命:从30行/小时到500+行/分钟,提升1000倍
- 质量飞跃:覆盖率从45-60%跃升至88-96%
- 全面覆盖:Happy Path + 边界条件 + 异常路径 + 属性测试
- 持续进化:代码变更后一键重新生成,保持测试同步
- 降低门槛:新手也能写出专业级测试用例
下一篇预告:《MonkeyCode遗留系统改造:AI助力老代码现代化》
浙公网安备 33010602011771号