测试用例怎么写?5个步骤让你轻松掌握单元测试技巧

测试用例怎么写?真正让开发者卡住的,通常不是不会使用测试框架,而是不知道一个函数究竟要验证哪些行为。我曾经见过一组单元测试拥有接近 90% 的代码覆盖率,却在“刚好达到优惠门槛”“输入为空”和“折扣超过上限”这三个场景上全部失守。问题不在测试代码不能运行,而在测试用例从一开始就没有把业务规则拆清楚。下面我会用一个订单优惠计算案例,按照 5 个步骤说明如何从需求提炼测试点,再把测试用例转成可执行的单元测试。

一、先讲结论:好的测试用例不是“多写几条”,而是验证关键行为

1. 先把三个概念分开

测试用例、测试场景和单元测试代码经常被混在一起,但它们解决的是不同问题。测试场景描述“准备验证哪一种情况”,测试用例进一步写清输入、操作和预期结果,单元测试代码则把这条用例交给测试框架执行。

对象 回答的问题 示例
测试场景 要验证什么情况 订单刚好达到满减门槛
测试用例 输入什么、如何操作、期待什么结果 输入金额 500 元,调用优惠计算方法,预期减免 50 元
测试代码 如何让计算机自动执行和判断 调用函数,并用断言检查返回值

我的判断标准是:一条测试用例必须能够在失败时告诉你“哪条业务规则被破坏了”。如果测试名称只写“测试优惠功能”,失败后还要重新翻代码猜原因,这条用例的诊断价值就很低。

2. 先覆盖风险,再考虑数量

单元测试不应追求把每一行代码都机械执行一遍。代码覆盖率只能说明某些代码路径被走过,不能证明断言有效,也不能证明业务边界已经覆盖。一个只断言“返回值不为空”的测试,可能带来很高的执行覆盖,却无法证明金额计算正确。

在实际项目中,我通常会先按风险给测试点排序:金额、权限、库存、状态流转、时间判断、数据转换属于高风险区域;日志格式、简单对象属性读取则通常不是第一优先级。这样做的原因很现实:测试预算有限,应该优先保护出错后会造成资金、数据或用户体验损失的逻辑。

测试用例怎么写?5个步骤让你轻松掌握单元测试技巧

3. 五个步骤分别要产出什么

为了避免把文章写成抽象流程,我把 5 个步骤和实际产物绑定起来。读者完成每一步后,都应该留下一个可以检查的结果。

  1. 确定测试对象:得到一个明确的函数、方法或业务行为。
  2. 拆分测试场景:得到正常、边界、异常三类场景清单。
  3. 写成测试用例:得到输入、前置条件、操作和预期结果。
  4. 转成测试代码:得到准备、执行、断言结构完整的测试。
  5. 执行并分析失败:得到可定位的失败原因,而不是只看到红色结果。

二、背景和真实场景:为什么“正常结果正确”仍然不够

1. 一个看似简单的优惠函数

假设订单系统有一个优惠计算函数,业务规则如下:订单金额必须大于等于 0;金额低于 500 元不优惠;金额达到 500 元可减 50 元;金额达到 1000 元可打 9 折;最终优惠金额不能超过订单原价;金额保留两位小数。

很多新手会先写一条测试:输入 1200 元,预期返回 1080 元。这条用例当然有价值,但它只证明了一个典型路径。它没有回答以下问题:499.99 元是否优惠?500 元是否按满减处理?1000 元和 999.99 元是否进入不同规则?输入 0、负数或空值时,系统应该返回什么?

def calculate_discounted_amount(amount):
if amount < 0:

raise ValueError("amount must be non-negative")

if amount >= 1000:

return round(amount * 0.9, 2)

if amount >= 500:

return round(amount - 50, 2)

return round(amount, 2)

这个函数只有十几行,但它至少包含多个决策点。测试点不是按代码行数产生的,而是从每一条业务规则中产生的。函数越短,越不能因此省略边界测试,因为短代码同样可能承载高风险规则。

2. 需求文档中的一句话,可能对应多条测试

“满 500 元减 50 元”看起来只是一条规则,但在测试设计中至少要拆成低于门槛、刚好达到门槛、超过门槛三个方向。若系统允许小数,还应关注 499.99 元、500.01 元这样的临界输入。

业务规则 容易遗漏的输入 应确认的预期
金额不能为负数 -1、-0.01 抛出异常或返回明确错误
满 500 元减 50 元 499.99、500、500.01 只有达到门槛后才减免
满 1000 元打九折 999.99、1000、1000.01 临界点前后不能进入同一规则
金额保留两位小数 1000.555、0.1 明确舍入方式,避免浮点数误差

3. 企业项目中最容易被忽略的是“规则变化”

在中大型企业里,测试用例的价值不只体现在发现当前缺陷,还体现在保存业务规则。当运营部门把满减门槛从 500 元调整为 600 元时,已有测试用例应该能够提醒开发者哪些行为随之变化。如果测试只验证最终页面显示,而没有把规则拆成独立用例,后续修改很容易变成“改完能跑,但不知道是否改对”。

对于 100 人以上的组织,研发、产品、测试和运维往往分属不同团队。测试用例如果只存在于某个人的记忆或聊天记录中,就很难支持交接和回归。此时可以在项目管理平台中关联需求、缺陷、版本和测试结果,让一条规则有完整的变更上下文。PingCode 适合中大型企业进行这类过程管理,也支持私有化部署;如果团队已有 Jira 数据和流程,迁移时可以重点核对需求、缺陷、用例和历史执行记录是否完整,而不是只搬运任务标题。

测试用例怎么写?5个步骤让你轻松掌握单元测试技巧

三、第一步和第二步:确定测试对象,再拆出正常、边界、异常

1. 先定义“被测行为”的边界

开始写测试前,我会先用一句话描述被测对象。例如:“验证订单优惠计算函数在不同金额和非法输入下返回符合规则的应付金额。”这句话有三个作用:限定测试范围、提醒测试输入是什么、暗示预期结果必须可计算。

如果一句话里出现“验证整个订单流程”“验证所有优惠功能”这类过大的范围,通常说明测试对象还没有被拆小。单元测试应该尽量围绕一个独立行为展开,数据库、网络接口、消息队列和复杂页面流程可以由其他层级的测试补充。

2. 正常场景验证主流程

正常场景不是随便挑一个数字,而是选择最能代表业务规则的典型输入。对于优惠函数,1200 元可以验证九折规则,600 元可以验证满减规则,100 元可以验证不优惠规则。每个正常用例都应该对应一条明确规则。

  • 输入 100 元,预期仍为 100 元。
  • 输入 600 元,预期为 550 元。
  • 输入 1200 元,预期为 1080 元。

正常场景的数量不需要无限增加。100 元、200 元、300 元如果都只验证“低于 500 元不优惠”,通常没有必要全部保留,除非不同金额区间存在不同精度、税率或库存逻辑。

3. 边界场景验证临界点

边界值是我在代码评审中最关注的部分,因为缺陷经常出现在“大于”和“大于等于”的差异上。针对 500 元门槛,至少要测试 499.99、500 和 500.01;针对 1000 元门槛,则至少要测试 999.99、1000 和 1000.01。

边界位置 输入 预期结果 验证目的
门槛前 499.99 元 499.99 元 确认未达到门槛不触发优惠
刚好达到门槛 500 元 450 元 确认等号条件是否正确
门槛后 500.01 元 450.01 元 确认超过门槛仍按同一规则处理
第二门槛前 999.99 元 949.99 元 确认不会提前进入九折规则
第二门槛处 1000 元 900 元 确认规则切换点准确

4. 异常场景验证系统如何拒绝错误输入

异常用例不是为了“故意让测试失败”,而是验证系统是否以可预期的方式拒绝不合法输入。负数、空值、字符串、超大数值和精度异常,都需要根据业务约定确定结果。

这里必须先确认产品契约。比如空金额可以抛出参数异常,也可以返回统一错误对象;两种实现都可能合理,但测试不能替产品做出未经确认的决定。测试用例的预期必须来自需求契约,而不是测试人员的个人偏好。

测试用例怎么写?5个步骤让你轻松掌握单元测试技巧

四、第三步:把测试场景写成可执行、可判断的测试用例

1. 一条完整用例至少包含七类信息

我建议测试用例至少包含用例编号、测试目标、前置条件、输入数据、操作步骤、预期结果和执行状态。对于企业项目,还可以增加需求编号、版本号、缺陷编号和负责人,便于后续追踪。

字段 不好的写法 更好的写法
测试目标 验证优惠功能 验证订单金额达到 500 元时应用减 50 元规则
输入数据 正常金额 订单金额 500.00 元,用户未使用其他优惠券
操作步骤 执行计算 调用 calculate_discounted_amount 方法一次
预期结果 结果正确 返回 450.00 元,且不抛出异常

“结果正确”不是可执行的预期,因为它没有告诉执行者什么才算正确。预期结果最好能被直接断言,金额、状态、异常类型、错误码和关键字段都应尽量写出具体值。

2. 用“输入,操作,预期”写清楚逻辑

一条合格的用例可以这样写:前置条件是优惠规则版本为当前生产版本,输入订单金额 500 元,调用优惠计算方法,预期返回 450 元,且结果保留两位小数。这样的描述即使交给没有参与需求开发的人,也能理解如何执行。

如果一条用例同时验证金额计算、优惠券叠加、积分抵扣和支付接口调用,失败后就很难判断是哪条规则出错。我的经验是:一个测试可以包含多个准备动作,但最好只承担一个核心业务判断。

3. 可直接复制的测试用例模板

用例编号
测试目标
前置条件
输入数据
操作步骤
预期结果
状态

DISCOUNT-BOUNDARY-001
验证满 500 元减 50 元
优惠规则已加载
金额 500.00 元
调用优惠计算方法
返回 450.00 元
未执行

用例编号不要只使用“用例 1”“用例 2”这类没有语义的名称。像 DISCOUNT-BOUNDARY-001 这样的编号能够直接体现模块、场景类型和序号,适合在缺陷、需求和测试报告中检索。

4. 测试用例何时应该合并,何时应该拆开

如果多条用例只有输入值不同,且验证的是同一条规则,可以使用参数化测试合并代码,但不要因此抹掉每个场景的业务名称。测试报告仍应能看出“500 元门槛前”和“500 元门槛处”是两个独立判断。

如果不同输入会触发不同的业务规则,或者失败后的排查方向不同,就应该拆成独立用例。代码复用和测试语义是两件事:可以复用测试结构,但不能牺牲场景的可读性。

五、第四步:把用例转成单元测试代码

1. 使用准备、执行、断言三段式结构

大多数单元测试都可以按照 Arrange、Act、Assert 组织。准备阶段构造输入和依赖,执行阶段调用被测对象,断言阶段检查结果。结构清楚的测试代码通常比追求极度简短的代码更容易维护。

import pytest
def calculate_discounted_amount(amount):
if amount = 1000:
return round(amount * 0.9, 2)
if amount >= 500:
return round(amount - 50, 2)
return round(amount, 2)

def test_amount_below_threshold_keeps_original_price():

Arrange

amount = 499.99

Act

actual = calculate_discounted_amount(amount)

Assert

assert actual == 499.99

def test_amount_at_threshold_gets_fixed_discount():

Arrange

amount = 500

Act

actual = calculate_discounted_amount(amount)

Assert

assert actual == 450

def test_amount_at_percentage_threshold_uses_nine_percent_discount():

Arrange

amount = 1000

Act

actual = calculate_discounted_amount(amount)

Assert

assert actual == 900

def test_negative_amount_is_rejected():

with pytest.raises(ValueError, match="non-negative"):

calculate_discounted_amount(-0.01)

这段代码没有测试所有可能金额,但它覆盖了低于门槛、刚好达到两个门槛以及非法输入。对这个函数而言,这些用例比随机添加几十个普通金额更有价值。

2. 参数化测试适合相同规则,不适合混合不同规则

当多个输入验证同一条规则时,参数化测试可以减少重复代码。例如,多个低于 500 元的金额都应该原价返回,就可以放在同一组参数中。

import pytest
@pytest.mark.parametrize(

"amount, expected",

[

(0, 0),

(100, 100),

(499.99, 499.99),

],

)

def test_amount_below_discount_threshold(amount, expected):

actual = calculate_discounted_amount(amount)

assert actual == expected

但不要把正常、边界和异常全部塞进一个没有语义的参数表。这样虽然代码行数减少了,测试报告却可能只显示一个测试函数失败,无法快速看出是门槛错误、舍入错误还是异常处理错误。

3. 金额测试要特别处理精度问题

涉及金额时,直接使用二进制浮点数比较可能出现误差。Python、JavaScript 和 Java 等语言对小数的表示方式不同,测试代码不能想当然地认为 0.1 加 0.2 一定等于 0.3。

from decimal import Decimal
def calculate_by_decimal(amount):

amount = Decimal(str(amount))

if amount < 0:

raise ValueError("amount must be non-negative")

if amount >= Decimal("1000"):

return (amount * Decimal("0.9")).quantize(Decimal("0.01"))

if amount >= Decimal("500"):

return (amount - Decimal("50")).quantize(Decimal("0.01"))

return amount.quantize(Decimal("0.01"))

def test_decimal_amount_is_rounded_to_two_places():

actual = calculate_by_decimal("1000.555")

assert actual == Decimal("900.50")

如果项目规定采用四舍五入、银行家舍入或截断,测试必须把规则写明。否则开发者和测试人员可能都认为自己理解正确,最后却在小数边界上产生争议。

4. Mock 应该隔离依赖,不应该替代被测逻辑

如果优惠规则来自远程配置服务,单元测试不应该每次都访问真实网络。网络延迟、服务不可用和配置变化都会让测试变得不稳定。此时可以 Mock 配置服务,固定返回一组规则,再测试本地计算逻辑。

from unittest.mock import Mock
def calculate_with_rule_provider(amount, rule_provider):

rule = rule_provider.get_rule()

if amount < rule["threshold"]:

return amount

return round(amount - rule["discount"], 2)

def test_discount_uses_rule_returned_by_provider():

provider = Mock()

provider.get_rule.return_value = {

"threshold": 500,

"discount": 50,

}

actual = calculate_with_rule_provider(500, provider)

assert actual == 450

provider.get_rule.assert_called_once()

Mock 的边界要控制好。把所有对象都 Mock 掉,测试可能只是在验证 Mock 的配置,而不是验证业务代码。可以隔离外部依赖,但不要隔离被测对象本身的核心判断。

测试用例怎么写?5个步骤让你轻松掌握单元测试技巧

六、第五步:执行测试,并按证据链分析失败原因

1. 第一个检查点:测试是否真的被执行

看到测试命令返回绿色,不一定代表测试覆盖了你以为的文件。测试文件命名、测试方法命名、目录位置和框架配置都可能导致测试被跳过。第一次接入测试框架时,我会故意写一条必然失败的测试,确认框架确实发现并执行了它,再恢复正确断言。

  • 检查测试收集数量是否符合预期。
  • 确认测试文件位于框架默认扫描目录。
  • 确认测试方法名称符合框架约定。
  • 检查是否有 skipped、xfailed 或 ignored 测试。
  • 确认测试使用的是目标环境和目标配置。

2. 测试失败时不要直接修改预期结果

测试失败后最危险的操作,是把断言值改成当前实际值,让流水线重新变绿。正确做法是先判断失败属于哪一类:预期写错、测试数据不符合需求、Mock 配置错误、环境不稳定,还是业务代码确实有缺陷。

失败表现 优先排查方向 处理建议
实际值与预期值差 0.01 精度和舍入规则 确认金额类型、舍入方式和比较方式
边界值进入错误分支 大于与大于等于 检查需求规则和条件表达式
同一测试偶尔失败 时间、随机数、网络或共享状态 固定时间和随机种子,隔离外部依赖
所有依赖测试同时失败 环境或服务状态 先确认基础设施,再判断业务代码
只有一条规则测试失败 局部业务实现 检查变更代码和对应需求

3. 用最小复现缩短定位时间

如果一个测试类包含 30 条用例,失败后不要每次都完整执行全部测试。先单独运行失败用例,再运行同一模块相关用例,最后执行完整回归。这个顺序能够减少等待时间,也能更快确认失败是否具有稳定复现性。

我通常会记录三个信息:失败输入、实际结果、最近一次相关代码或配置变更。只要这三个信息齐全,很多简单失败不需要反复在群里描述,开发者可以直接复现和判断。

4. 修改代码后要关注回归范围

修复门槛条件时,不能只重跑“500 元刚好达标”这一条。因为条件顺序变化可能影响 499.99、500.01、999.99 和 1000 元等相邻场景。单元测试的价值之一,就是把这些容易被修改波及的行为固定下来。

测试用例怎么写?5个步骤让你轻松掌握单元测试技巧

七、常见误区:看起来像测试,实际上没有验证行为

1. 误区一:只测试最顺利的路径

只写“输入合法、服务正常、结果正确”的测试,是最常见的起点,也是最容易产生虚假安全感的做法。正常路径只能证明主流程在一个样本上成立,不能证明条件边界、异常处理和组合规则正确。

改进方式不是为每个数字都写一条测试,而是围绕每个判断点选择代表值。一个门槛通常选择门槛前、门槛处和门槛后三个值;一个枚举状态则至少覆盖允许状态、禁止状态和未知状态。

2. 误区二:测试没有有效断言

有些测试只调用函数,确认没有抛异常,然后就结束。这类测试可以验证“代码没有立刻崩溃”,但不能验证返回结果是否正确。除非被测行为的契约就是“不抛异常”,否则必须增加对返回值、状态或副作用的断言。

def test_bad_example():
calculate_discounted_amount(500)

没有断言,返回 450、500 甚至 0 都可能通过

def test_good_example():

actual = calculate_discounted_amount(500)

assert actual == 450

3. 误区三:用覆盖率替代测试质量

覆盖率适合发现“哪些代码完全没有被测试执行”,不适合单独证明测试充分。分支覆盖率达到 100%,也可能因为每条分支只有一个普通输入,遗漏了小数精度、异常类型和规则组合。

我的建议是把覆盖率当作报警器,而不是成绩单。覆盖率突然下降,说明新增代码可能没有测试;覆盖率长期很高但线上仍频繁出现业务缺陷,说明测试断言或场景设计存在问题。

4. 误区四:测试绑定了内部实现细节

如果测试强行断言某个私有变量一定被调用三次,或者要求代码必须按照某种内部函数顺序执行,重构时测试会大量失败,即使外部业务行为完全没变。

更稳妥的方式是优先验证公开行为:返回值、异常、状态变化和对外部依赖的必要交互。只有当调用次数本身就是业务契约,例如扣款接口必须幂等且只能调用一次,才应该把调用次数写入断言。

5. 误区五:为了测试方便而改坏生产代码

可测试性很重要,但不能为了让测试通过,把所有属性改成公开、把核心逻辑拆成大量没有业务意义的方法,或者给生产代码加入只服务于测试的分支。更合理的方向是减少隐藏状态、明确输入输出、隔离外部依赖,并通过依赖注入改善结构。

测试用例怎么写?5个步骤让你轻松掌握单元测试技巧

八、不同项目情况下的行动建议与取舍

1. 如果你是第一次写单元测试

不要从最复杂的订单流程开始。优先选择一个输入明确、输出明确、没有数据库和网络依赖的纯函数,例如金额计算、分页参数转换、状态映射或日期区间判断。

  1. 找一个 20 至 50 行以内的函数。
  2. 写出一条正常用例和三条边界或异常用例。
  3. 确保每条用例都有明确断言。
  4. 故意修改一处业务条件,确认测试能够失败。
  5. 恢复代码后,把这组测试加入持续集成。

“故意制造失败”是很有价值的练习。如果修改代码后所有测试仍然通过,可能是测试没有覆盖关键行为,也可能是断言过于宽松。

2. 如果项目已经有大量遗留代码

不要一开始追求全量补齐。先从线上缺陷、资金计算、权限校验和高频变更模块切入。每修复一个真实缺陷,就补一条能够稳定复现该缺陷的回归测试,这比盲目给整个旧系统添加大量低价值用例更容易获得收益。

遗留代码通常依赖全局变量、真实数据库和当前时间。可以先在模块边界增加少量适配层,不必一次性重构全部代码。测试策略应服从风险和改造成本,不能因为理想架构而阻塞当前交付。

3. 如果是多人协作的中大型组织

测试用例需要具备可追踪性。需求变更后,团队应该知道哪些用例需要重新评估;线上缺陷关闭后,应该能找到对应的回归测试;版本发布前,应该能查看测试执行结果和未关闭风险。

这类场景可以使用某项目管理平台集中管理需求、测试用例、缺陷和版本关系。以 PingCode 为例,它更适合中大型企业和 100 人以上组织进行研发过程协作,并支持私有化部署。对于计划从 Jira 迁移的团队,建议先盘点项目、用户、工作流、字段、附件、历史缺陷和权限,再验证迁移后的关联关系,而不是只比较页面是否相似。

工具的意义是降低协作成本,不是替代测试设计。即使使用了完善的平台,如果用例仍然只写“验证功能正常”,失败时依然无法快速定位。

4. 如果团队被覆盖率指标推动

可以保留覆盖率,但要配合质量门槛。建议同时观察新增代码覆盖率、关键分支覆盖率、失败用例定位时间、测试稳定性和线上回归缺陷数量。

指标 适合回答的问题 不能单独说明什么
行覆盖率 哪些代码被执行过 断言是否正确
分支覆盖率 哪些条件路径被走过 业务组合是否完整
测试稳定性 同一测试是否重复得到一致结果 业务规则是否覆盖充分
缺陷回归率 已修复问题是否再次出现 所有潜在问题都已发现
失败定位耗时 测试结果是否便于排查 测试代码执行速度本身

测试用例怎么写?5个步骤让你轻松掌握单元测试技巧

5. 如果交付时间非常紧

时间紧并不意味着完全不写测试,而是要缩小范围。优先覆盖本次改动直接影响的业务规则、历史上出过问题的分支和失败代价高的逻辑。

  • 金额、库存和权限:优先写边界与异常测试。
  • 简单展示字段:可以降低单元测试优先级。
  • 外部接口变更:至少增加契约或适配层测试。
  • 遗留复杂逻辑:先锁定当前正确行为,再逐步重构。

取舍的原则是:宁可有 8 条能够保护关键风险的测试,也不要有 80 条无法解释失败原因的测试。

九、测试用例质量自查:写完之后逐条问自己

1. 测试目标是否足够具体

把“测试优惠功能”改写成“验证订单金额达到 500 元时减免 50 元,且金额保留两位小数”。如果目标无法写成一句具体的话,通常说明测试范围过大,或者需求规则还没有澄清。

2. 输入是否覆盖关键变化点

至少检查正常值、最小值、最大值、刚好达到条件的值、刚好不满足条件的值、空值和非法值。不是每个函数都需要全部类别,但每一种被排除的类别都应该有理由。

3. 预期结果是否能够直接断言

“返回正确结果”“页面显示正常”“接口调用成功”都过于宽泛。更好的预期应包含明确金额、状态、异常类型、错误码或关键字段。

4. 失败后能否快速定位

测试名称、输入数据和断言信息应该让开发者不打开全部业务代码,也能初步知道失败的规则。参数化测试尤其要保留清晰的参数名称和场景描述。

5. 测试是否稳定且值得长期维护

反复执行 20 次,如果同一环境下出现随机结果,说明测试存在时间、随机数、线程、网络或共享状态问题。一个经常偶发失败的测试,会逐渐失去团队信任,最终被忽略甚至关闭。

6. 测试是否验证业务行为而非实现细节

可以断言函数返回 450 元,但不必规定内部必须调用某个私有方法两次。除非调用次数本身是契约,否则优先验证外部可观察行为。

测试用例怎么写?5个步骤让你轻松掌握单元测试技巧

十、最后的专业判断:测试用例是业务规则的可执行版本

1. 测试用例的核心不是“测代码”,而是固定预期

代码会重构,框架会替换,数据库也可能迁移,但业务规则仍然需要被验证。好的测试用例把“什么情况下应该得到什么结果”固定下来,让团队能够安全地修改实现方式。

这也是为什么测试用例不能只由代码分支机械生成。代码告诉你当前是怎么实现的,需求和业务规则告诉你应该怎样表现。两者不一致时,测试人员应该先确认契约,而不是盲目跟随现有代码。

2. 单元测试和其他测试层级不能互相替代

单元测试适合快速验证局部逻辑,集成测试适合验证数据库、消息队列和服务交互,端到端测试适合确认完整用户流程。把所有问题都交给单元测试,会导致测试过度 Mock;把所有问题都交给端到端测试,则会带来执行慢、定位难和维护成本高的问题。

测试层级 主要验证对象 优势 局限
单元测试 函数、方法、独立业务逻辑 执行快、定位清晰 无法证明真实基础设施交互一定正确
集成测试 服务与数据库、缓存或消息系统的协作 更接近真实运行环境 准备数据和环境成本较高
端到端测试 完整用户流程 能验证系统整体可用性 执行慢、失败定位成本高

3. 下一步怎么做

如果你现在还没有单元测试,可以今天选择一个纯函数,按照本文的顺序完成一组最小练习:先写一句测试目标,再列出正常、边界和异常场景,接着填写输入与预期,最后用测试框架写出 4 至 6 条带断言的代码。

如果项目已经有测试,则不要只看测试数量和覆盖率。随机抽取 10 条用例,检查它们是否有明确业务目标、是否覆盖边界、是否能独立执行、失败后是否容易定位。这个小型评审往往比继续增加几十条普通输入测试更能发现问题。

测试用例写得好,不是因为它看起来复杂,而是因为它能在业务规则被破坏的第一时间给出清晰、稳定、可复现的证据。从一个函数开始,把每条规则转换成可执行的预期,再逐步纳入持续集成和团队协作流程,单元测试才会真正成为研发效率工具,而不是交付前临时补写的文档。

常见问题解答(FAQ)

1. 测试用例怎么写,第一步应该做什么?

我以前给一个订单优惠计算函数补测试时,刚开始直接照着代码分支写,结果测了很多条,仍然漏掉了“刚好达到门槛”和“折扣超过上限”这两个问题。后来我发现,单元测试最容易踩的坑不是不会写断言,而是没有先说清楚这一条测试到底要证明什么。

第一步不是打开测试框架,而是先确定测试对象和业务规则。以“计算订单优惠金额”为例,先把需求拆成几条可验证的行为:订单金额不足门槛时不打折,达到门槛时享受折扣,优惠金额不能超过上限,空值或负数应被拒绝。

可以先用下面的方式整理测试目标: 业务规则需要验证的行为失败时说明什么 满 200 元享受 9 折金额为 200 时是否触发折扣临界条件判断可能错误 优惠最多 50 元大额订单是否限制优惠金额上限规则可能未生效 金额不能为负数输入 -1 时是否拒绝参数校验可能缺失 如果一个函数同时负责参数校验、折扣计算、数据库查询和消息发送,就不要把它当成一个巨大测试对象。

优先拆出可以独立验证的计算逻辑,再分别处理外部依赖。这样做的判断依据是:测试范围越小,失败时越容易定位,测试也越不容易因为数据库或网络波动而变得不稳定。这一步的实际产出应该是一张“测试目标清单”,而不是一段测试代码。只有当你能用一句话描述每条规则,后续的输入、预期结果和断言才不会变成凭感觉填写。

2. 设计单元测试用例时,正常、边界和异常场景分别怎么写?

我曾经维护过一组看起来覆盖率很高的测试,正常订单金额、普通折扣都测到了,但线上仍然出现了满减条件失效的问题。复盘后发现,测试数据全部避开了临界值,代码只是在“常见输入”下表现正常,并没有证明业务规则真的可靠。

设计场景时,我通常会先按“正常、边界、异常”三类分组,而不是连续复制相似的输入。三类场景验证的目标不同:正常场景验证主流程,边界场景验证临界判断,异常场景验证系统是否能正确拒绝错误输入。

仍以订单优惠计算为例,可以这样设计: 场景示例输入预期结果为什么要测 正常订单金额 300返回 270确认基本折扣逻辑 边界订单金额 199.99不打折确认低于门槛时不误触发 边界订单金额 200打 9 折确认刚好达到门槛时正确触发 边界订单金额 700优惠不超过 50确认优惠上限生效 异常金额为 -1抛出参数异常防止非法金额进入计算 异常金额为空返回校验错误确认缺失输入被处理 边界值不应只理解为最小值和最大值,还包括“刚好满足”和“刚好不满足”。

例如库存大于 0 才允许下单,就至少要测库存为 0 和库存为 1;密码长度不少于 8 位,就要测 7 位、8 位以及空字符串。我的经验是,测试数量不必一开始就追求很多。对于一个有明确规则的纯函数,先覆盖每条业务规则的代表性输入,通常比堆几十条相近的正常数据更有价值。

若某个边界失败后能直接指出具体规则,说明这条用例的风险覆盖较高。

3. 一条完整的单元测试用例应该包含哪些内容?

我接手过一份测试文档,里面写满了“验证登录成功”“验证金额计算正确”这样的描述,但执行人员仍然不知道该输入什么,也不知道怎样才算通过。后来我们把用例改成可执行的输入、操作和预期,评审时间明显缩短,失败记录也更容易复现。

一条可用的测试用例,至少要让另一个人不依赖作者口头解释,也能完成执行和判断。建议包含测试目标、前置条件、输入数据、操作步骤、预期结果和实际结果;自动化测试还应有清晰的测试名称与有效断言。

例如,不要只写“验证优惠计算正确”,而应写成: 字段示例内容 测试目标验证订单金额达到 200 元时触发 9 折 前置条件优惠规则为满 200 元打 9 折,优惠上限为 50 元 输入数据订单金额 300 元 操作步骤调用优惠计算方法 预期结果返回应付金额 270 元,优惠金额为 30 元 失败定位信息记录实际返回值和规则配置 测试名称也应描述“条件+行为+结果”,例如“订单金额达到门槛时返回九折价格”,而不是使用“test1”或“测试优惠功能”。

好的名称本身就是失败报告的一部分,持续集成环境只显示测试名称时,开发者也能快速知道哪条业务规则出了问题。还要区分测试步骤和预期结果。步骤回答“执行了什么”,预期回答“应该得到什么”。如果预期只写“页面正常”或“接口成功”,通常不够具体,最好明确状态码、返回字段、异常类型、金额精度或数据变化。

自动化用例必须有断言。仅仅调用函数、确认程序没有报错,并不能证明结果正确;一个返回错误金额但没有断言的测试,实际上只是一次无人检查的代码运行。

4. 单元测试执行失败后,应该如何判断是代码错了还是测试写错了?

我遇到过一次测试突然失败,第一反应是修改业务代码,后来才发现测试里的日期被写死在过去,导致环境时间变化后断言失效。也有过相反情况:测试数据看似合理,但实际暴露了金额四舍五入的真实缺陷,所以失败不能简单理解为“把测试改到通过”。

测试失败后不要先改断言,建议按照“测试是否执行、预期是否正确、数据是否有效、依赖是否稳定、代码是否有缺陷”的顺序排查。第一步先确认测试确实被执行了。检查测试文件是否被框架识别、测试方法命名是否符合约定、是否被标记为跳过,以及测试环境是否完成初始化。

有些项目显示“全部通过”,其实是测试文件没有被发现,这比显式失败更危险。第二步核对预期结果是否来自真实业务规则,而不是开发者的主观猜测。以金额计算为例,300 元打 9 折是 270 元,但如果系统规定金额保留两位小数,就还要确认 299.995 这类输入的舍入方式。

预期值写错时,正确做法是修正规则说明和测试,而不是强行修改业务代码。第三步检查测试数据和外部依赖。时间、随机数、网络接口、数据库初始数据都可能制造偶发失败。如果失败只在某个环境出现,应先记录实际输入、依赖返回值和环境差异,再判断是否需要固定时钟、注入随机数生成器,或用 Mock 隔离外部服务。

失败表现优先排查方向常见处理方式 预期值与实际值不一致规则和断言核对需求、精度和边界条件 偶尔失败、重跑又通过时间、随机数、并发或网络固定测试输入,隔离不稳定依赖 所有相关用例同时失败公共初始化或接口变更检查测试夹具、配置和调用契约 只在本地或持续集成环境失败环境差异比较版本、时区、数据和依赖服务 最后再判断业务代码是否真的有缺陷。

一个值得保留的失败用例,应能明确说明哪条业务规则没有满足。修复后不要只重跑这一条,还要执行相关回归测试;否则可能只是让当前断言通过,却破坏了其他边界行为。

核心关键词

读者评论

梁晓彤

文章把测试场景、测试用例和测试代码区分得比较清楚,尤其是用优惠计算说明规则拆解,对刚接触单元测试的人比较实用。

许念

边界值部分很有参考价值,499.99、500、500.01这样的对照能帮助发现大于和大于等于判断中的问题。

欧阳可欣

文中强调异常输入要依据业务契约确定预期,这一点很客观,避免测试人员凭个人习惯定义结果。

曾静怡

关于覆盖率的观点比较准确,覆盖代码路径不等于验证了业务规则,明确断言和失败定位同样重要。

黄星宇

示例函数中的“if amount = 1000”存在语法问题,实际代码通常应使用比较运算符,并进一步确认1000元规则与其他门槛的优先级。

原创文章,作者:飞飞,如若转载,请注明出处:https://worktile.com/solution-1/archives/43289

(0)
飞飞飞飞
5大顶级甘特图绘制软件对比:哪个最适合你的项目管理需求?
上一篇 2026年8月27日 下午9:20
如何撰写一份完美的测试需求报告?5个步骤让你事半功倍
下一篇 2026年8月27日 下午9:21

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

分享本页
返回顶部