01 真实场景:物流系统「运费计算」模块的迭代困境
某物流公司核心业务系统,支持用户在线下单。订单提交时,系统自动根据「重量、体积、目的地、时效类型(标准/加急)」计算运费。
一天,产品经理要求:在「加急」时效下,为VIP用户打8折。
开发在计算逻辑中新增了一个判断分支:
python
if user.is_vip and delivery_type == "urgent":
total_fee = base_fee * 0.8
修改完成后,测试团队面临两个问题:
1. 新功能正确吗?(功能测试)
2. 旧功能被破坏了吗?(回归测试)
02 问题拆解:功能测试 vs 回归测试,各自查什么
| 维度 | 功能测试 | 回归测试 |
| 目标 | 验证新功能按需求工作 | 验证旧功能未被新代码影响 |
| 焦点 | 新增的逻辑分支 | 所有已有逻辑分支 |
| 范围 | 狭、深 | 宽、浅(但关键路径要深) |
| 执行时机 | 每个版本迭代初期 | 功能测试通过后、版本发布前 |
回到物流场景:
- ►功能测试:只测VIP + 加急 + 打8折这条路径。
- ►回归测试:必须覆盖所有运费计算路径(普通用户、标准时效、超大件、跨省、同城…)。
03 逐步解决:代码级的测试策略
被测对象:运费计算引擎(简化版)
python
# freight_engine.py
class FreightEngine:
def __init__(self):
self.base_rate = 1.5 # 每公斤每公里基础费率
self.urgent_rate = 2.0
def calculate(self, weight, distance, user_type, delivery_type):
# 基础运费
if delivery_type == "urgent":
rate = self.urgent_rate
else:
rate = self.base_rate
fee = weight * distance * rate
# VIP加急折扣(新增逻辑)
if user_type == "vip" and delivery_type == "urgent":
fee *= 0.8
# 最低消费
if fee < 10:
fee = 10
return round(fee, 2)
功能测试代码:只验证新增分支
python
# test_functional.py
import unittest
from freight_engine import FreightEngine
class TestVIPUrgentDiscount(unittest.TestCase):
"""功能测试:只测VIP+加急的打折路径"""
def setUp(self):
self.engine = FreightEngine()
def test_vip_urgent_discount_applied(self):
# VIP用户,加急,10kg,100km → 原价2000,折扣后1600
fee = self.engine.calculate(10, 100, "vip", "urgent")
self.assertEqual(fee, 1600.00)
def test_vip_urgent_min_fee_respected(self):
# 极小重量:1kg,1km,VIP加急 → 原价2,折扣后1.6,但最低消费10
fee = self.engine.calculate(1, 1, "vip", "urgent")
self.assertEqual(fee, 10.00)
if __name__ == "__main__":
unittest.main()
回归测试代码:覆盖所有运费路径
python
# test_regression.py
import unittest
from freight_engine import FreightEngine
class TestFreightRegression(unittest.TestCase):
"""回归测试:确保旧逻辑不被破坏"""
def setUp(self):
self.engine = FreightEngine()
# 旧路径1:普通用户+标准时效
def test_standard_normal(self):
fee = self.engine.calculate(10, 100, "normal", "standard")
self.assertEqual(fee, 1500.00) # 10*100*1.5
# 旧路径2:普通用户+加急(无折扣)
def test_urgent_normal(self):
fee = self.engine.calculate(10, 100, "normal", "urgent")
self.assertEqual(fee, 2000.00) # 10*100*2.0
# 旧路径3:VIP+标准(无折扣)
def test_standard_vip(self):
fee = self.engine.calculate(10, 100, "vip", "standard")
self.assertEqual(fee, 1500.00) # 10*100*1.5
# 边缘情况:重量为0
def test_zero_weight(self):
fee = self.engine.calculate(0, 100, "normal", "standard")
self.assertEqual(fee, 10.00) # 低于最低消费
# 边缘情况:超大重量
def test_large_weight(self):
fee = self.engine.calculate(1000, 100, "vip", "urgent")
# 1000*100*2.0*0.8 = 160000.00
self.assertEqual(fee, 160000.00)
if __name__ == "__main__":
unittest.main()
04 效果验证:两个测试套件协同运行
text
# 先跑功能测试
$ python test_functional.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
# 再跑回归测试
$ python test_regression.py
.....
----------------------------------------------------------------------
Ran 5 tests in 0.002s
OK
如果回归测试失败,比如 test_urgent_normal 报错:
text
AssertionError: 1600.0 != 2000.0
说明新增的VIP判断错误地影响了普通用户的加急运费——可能 user_type 默认值处理有误。此时必须回滚或修复代码,确保旧路径不受干扰。
05 方法论提炼:协作关系与出色实践
| 步骤 | 做什么 | 谁负责 |
| 1 | 写功能测试,覆盖所有新增需求分支 | 开发或测试工程师 |
| 2 | 写回归测试,覆盖所有已有关键路径 | 测试工程师(持续维护) |
| 3 | 先跑功能测试 → 通过后合并代码 | CI流水线 |
| 4 | 合并后跑回归测试 → 全部通过才发布 | CI流水线 |
关键原则:
- ►回归测试用例库需要持续更新:每次新增功能,把该功能的测试用例加入回归套件。
- ►回归测试优先保证核心流程(如物流系统的运费计算、订单创建、支付流转)。
- ►功能测试追求深度(每个分支、每个边界),回归测试追求广度(覆盖所有模块的典型路径)。
---
功能测试保证新代码做对了事,回归测试保证旧代码没做错事——两者缺一不可,共同构成版本发布的安全网。