ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

软件测试用例设计实战:佣金问题等价类与边界值全解析

软件测试用例设计实战:佣金问题等价类与边界值全解析

搞软件测试的人,十有八九都遇到过这套经典练习:“给你一个佣金计算需求,请设计测试用例。”别小看它,我见过不少简历上写着“精通测试用例设计”的人,在这道题上翻车。原因很简单,它表面上就是一道小学应用题:三种零件各卖出多少件、单价多少、按销售额分档提成。可一到写用例的时候,等价类划不划得清、边界值找不找得全、不同条件组合会不会漏,才是真正的分水岭。这篇文章就用这个佣金问题做载体,把测试用例设计的完整过程从头到尾捋一遍。适合三类人看:刚入行的测试新人,准备面试的候选人,以及需要在团队里做测试设计内训的同伴。我会直接讲怎么做、为什么这么做、以及实际执行时踩过的坑。

1. 需求拆解:佣金问题的业务规则和隐含假设

1.1 经典需求版本

我这里用的是软件测试课程里最常见的佣金问题版本。某公司销售三种零件A、B、C,单价分别为45元、30元、25元。销售人员每月上报三种零件的销售数量,均为非负整数,且每种数量不超过70件。系统需要根据输入计算出两个输出:当月总销售额和应发佣金。

佣金规则按销售额分三段提成:

  • 销售额不超过1000元的部分,佣金比例为10%
  • 超过1000元但不超过1800元的部分,佣金比例为15%
  • 超过1800元的部分,佣金比例为20%

用公式写出来更清晰:

销售额 = 45 × A + 30 × B + 25 × C

  • 若销售额 ≤ 1000:佣金 = 销售额 × 10%
  • 若 1000 < 销售额 ≤ 1800:佣金 = 100 + (销售额 - 1000) × 15%
  • 若 销售额 > 1800:佣金 = 220 + (销售额 - 1800) × 20%

其中100是前1000元的10%提成,220是前1000元提成100加上中间800元提成120的总和。这两个常数建议在用例设计之前先手算一遍,因为很多预期结果写错,源头就是这里。

1.2 为什么这道题适合做测试设计练习

佣金问题的魅力在于“需求短、规则清楚、坑却一点不少”。它有三个输入变量,每个都有范围约束,中间还有一个分段函数,输出又分成销售额和佣金两项。这种结构天然覆盖了等价类划分、边界值分析、判定表驱动这三种最核心的黑盒测试设计方法,而且业务本身贴近真实的计费系统,练完可以直接迁移到电商提成、运费模板、阶梯折扣等场景。

做这道题的时候,很多新人会陷入一个误区:以为测试用例就是随便填几组数字,跑一遍对一下结果就行。实际上,面试官或者带你的老测试想看到的是你能否从需求里提炼出边界条件,能否识别出需求中没说清楚的地方,能否用测试设计方法而不是“感觉”来生成用例。这才是一道好练习题的价值。

1.3 需求里没写,但设计用例前必须确认的事

真实项目中不存在“需求一看就懂”的情况,这道练习题也一样。以下是我每次带新人做这个练习时,会要求对方先回答的几个问题:

  1. 佣金保留几位小数?题目多半只说“佣金是多少元”,但真实业务里佣金通常精确到分,也就是0.01元。不确认清楚,预期结果的小数位怎么定?
  2. 销售数量允许为0吗?0件算正常上报还是异常数据?经典题目里数量是非负整数,那0是有效等价类。
  3. 输入类型是什么?UI输入框、接口入参测的范围不一样。如果是接口,字符串、NULL、浮点数都可能传到服务器;如果是表单,前端可能会先拦截一部分。
  4. 数量超过70怎么处理?直接报错还是截断?负数、小数、字符分别返回什么提示?
  5. 佣金计算如果出现浮点误差,是四舍五入还是截断保留?这一点在真实计费系统里最容易出线上事故。

这些疑点应该在测试计划里写成“待确认问题”,而不是自己拍脑袋定一套预期结果。等产品和开发给了明确结论,再锁定用例。这一条本身就是测试用例设计的基本功。

2. 测试设计方法怎么用:不是堆用例,而是防漏

2.1 等价类划分:先把大格子分出来

等价类划分的核心思想是把无限多的输入归成有限几类,从每类里取一个代表值去测,认为这类输入的表现是一致的。对佣金问题来说,可以这样划分:

输入A、B、C的取值分别都有:

  • 有效等价类:0 ≤ 数量 ≤ 70,且为整数
  • 无效等价类:数量 < 0
  • 无效等价类:数量 > 70
  • 无效等价类:数量为小数或非数值

输出的等价类可以从销售额和佣金两个维度看:

  • 销售额有效范围:0 ≤ 销售额 ≤ 7000,因为三个数量全为70时销售额是最大,即 (45+30+25) × 70 = 7000
  • 佣金结果有效范围:0 ≤ 佣金 ≤ 1260,全70时佣金 = 220 + (7000-1800) × 20% = 220 + 1040 = 1260
  • 佣金分段区间:销售额 ≤ 1000、1000 < 销售额 ≤ 1800、销售额 > 1800

这里有个新手容易掉进去的坑:把“销售额区间”当成输入变量的边界。销售额是三个输入组合之后的结果,不是直接输入值,所以你不能只测A=1000这种本身不合法的样例,而是要通过调整A、B、C的数量,让算出来的销售额恰好落在分段边界附近。

2.2 边界值分析:单变量边界和组合边界都不能漏

边界值分析在等价类的基础上,重点考察每个等价类的边界及其附近值。佣金问题里有两类边界必须分别处理。

第一类是输入数量的边界。A、B、C各自的合法边界都是0和70,所以合法边界内要覆盖0、1、69、70,非法边界要覆盖-1、71。这个比较简单,但要记住三个输入变量都要测,不能只拿A举例。

第二类是销售额分段边界,也就是1000元和1800元这两个佣金阈值。难点在于,你需要找出对应的A、B、C组合使销售额恰好等于这些阈值。这里有一个非常实际的数学限制:45、30、25都是5的倍数,所以无论怎么组合,销售额一定是5的倍数。这意味着1001、1799这种值根本构造不出来,边界附近的用例只能取最接近的可达值。

我常用的构造方式是固定其中两个变量,只调整第三个。比如固定A=0、B=0,只调C:

  • C=39,销售额=975,落在第一档,佣金=97.5
  • C=40,销售额=1000,正好是第一档的上边界,佣金=100
  • C=41,销售额=1025,落在第二档,佣金=100+25×15%=103.75

这样就把1000元边界的左右两侧都覆盖到了。1800元边界可以用A=19、B=3、C=34来逼近:销售额=19×45+3×30+34×25=855+90+850=1795,佣金=100+795×15%=219.25;再用A=19、B=4、C=33,销售额=855+120+825=1800,佣金=220。上下两组数据把第二档和第三档的交界处卡得死死的。

2.3 判定表:多条件组合防漏神器

等价类和边界值适合处理单输入或单边界的场景,但佣金问题存在多条件组合。三个数量是否合法,加上销售额落在哪个档位,组合起来很容易漏测。判定表在这里的价值就是强制枚举所有条件组合。

条件桩可以设为:

  • A是否在合法范围:是/否
  • B是否在合法范围:是/否
  • C是否在合法范围:是/否
  • 销售额所处的档位:第一档/第二档/第三档

动作桩则是:

  • 提示“输入数量非法”
  • 计算销售额
  • 计算佣金
  • 返回销售额和佣金

理论上三个合法性条件有8种组合,再叠加三个销售额档位,规则会比较多。但实践上不需要机械地为每个组合都造一条用例,比如A、B、C都合法的8种覆盖每个档位各来几条,而只要有一个非法就统一走提示逻辑。判定表的核心价值在于推动你思考哪些条件组合没有被覆盖,而不是让用例数量膨胀。

2.4 场景法和因果图怎么补充

有的教材会在这道题里硬套因果图,其实不必要。佣金问题是一个典型的纯计算函数,因果图能画出来的“数量非法→提示错误”“数量合法→计算佣金”这种关系,用等价类就足够表达。场景法更适合去测端到端流程:用户打开录入页面、填写数据、点击提交、看到佣金结果。如果你在一个GUI系统里做佣金模块,那场景法可以补几条冒烟用例,比如连续提交、清空重填、页面刷新后的状态等。

我的建议是,做练习题阶段不要贪多,等价类+边界值+判定表就够了。方法是为目的服务的,一个函数模块非要上全量场景法,反而会稀释真正重要的边界用例的价值。

3. 可直接参考的测试用例清单

3.1 功能与边界用例表

下面是我整理的一套完整用例,编号和预期结果都经过手算验证。设计依据列说明这一条用例到底覆盖了什么测试设计点。

用例编号ABC预期销售额预期佣金设计依据
TC-FUNC-011010101000100.00一般组合,销售额正好等于第一档上限
TC-FUNC-021010111025103.75第二档起始附近,C加1越过1000
TC-FUNC-03194331800220.00销售额正好等于第二档上限
TC-FUNC-04195321805221.00第三档起始附近
TC-FUNC-0500000.00最小合法组合
TC-FUNC-0670707070001260.00最大合法组合
TC-FUNC-0755550050.00第一档中间值
TC-FUNC-083029282920444.00第三档中间值组合
TC-BOUND-010101055055.00A的下边界
TC-BOUND-021101059559.50A下边界+1
TC-BOUND-036910103655591.00A上边界-1
TC-BOUND-047010103700600.00A上边界
TC-BOUND-051003997597.50销售额1000左侧最近可达值
TC-BOUND-061010101000100.00销售额1000精确边界
TC-BOUND-071010111025103.75销售额1000右侧最近可达值
TC-BOUND-08193341795219.25销售额1800左侧最近可达值
TC-BOUND-09194331800220.00销售额1800精确边界
TC-BOUND-10195321805221.00销售额1800右侧最近可达值

注意TC-FUNC-03和TC-BOUND-09重复了,实际项目里可以合并,这里分开是为了说明“功能设计”和“边界设计”关注点不同。每一行我都会让新人手算一遍,尤其是佣金,很多人会把第二档的100元基础提成漏掉,比如销售额1800直接算成1800×15%=270,那预期结果就全错了。

3.2 非法输入与异常用例表

非法输入的预期结果不一定是“抛异常”,更合理的预期是“系统给出友好且明确的错误提示,并且不计算佣金”。这条要在需求评审时和产品对齐,否则开发会认为“你塞个负数进来,我返回500也算合理”?

用例编号输入预期结果设计依据
TC-INV-01A=-1, B=0, C=0提示“数量不能为负”负数无效等价类
TC-INV-02A=0, B=71, C=0提示“单种数量不能超过70”超上限无效等价类
TC-INV-03A=0, B=0, C=1.5提示“数量必须为整数”小数无效等价类
TC-INV-04A=0, B=0, C=abc提示“请输入数字”非数值字符
TC-INV-05A=空值, B=0, C=0提示“数量不能为空”空值输入
TC-INV-06A=-1, B=71, C=abc按规则提示第一个错误或汇总所有错误多条件非法组合

最后一条的预期结果尤其需要提前确认。如果系统设计为逐字段校验,那-1会先被拦截;如果设计为统一校验,则可能一次性提示三个问题。两种实现都合理,但测试断言必须跟着设计走。

3.3 精度相关的用例

佣金涉及小数,浮点精度是必测点。我建议单独建一组用例,专门验证小数位数和舍入规则。

用例编号ABC预期销售额预期佣金说明
TC-PREC-0100661650197.50650×15%计算结果存在二进制浮点误差风险
TC-PREC-0200521300145.00300×15%=45,简单小数但不一定精确
TC-PREC-032220201990238.00第二档+第三档混合,金额非整数
TC-PREC-0400000.00零值边界,检查是否出现-0.00

为什么单独提1650这个值?我用Python实测过,650×0.15的结果在某些环境下会等于97.49999999999999,再加上100就是197.49999999999997。如果程序直接把浮点数转字符串展示,用户会看到197.49999999999997,这就是缺陷。真实业务里佣金元以下都要四舍五入到分,常见做法是用十进制类型或者把所有金额转成分(整数)再计算。测试时要确认实现方案,再决定断言怎么写在自动化脚本里。

3.4 需求追踪矩阵

如果这是真实项目,我还会做一张简单的追踪矩阵,把每条测试用例和需求条目对应起来。佣金问题的需求条目可以拆成三部分:数量合法性校验、销售额计算、佣金分段计算。

需求项覆盖用例状态
REQ-01 数量必须是非负整数且≤70TC-BOUND-01~04,TC-INV-01~06已覆盖
REQ-02 销售额=45A+30B+25CTC-FUNC-01~08已覆盖
REQ-03 佣金按销售额分三段计算TC-BOUND-05~10已覆盖
REQ-04 金额保留两位小数TC-PREC-01~04已覆盖

追踪矩阵的目的是回答“你测了什么、凭什么说测完了”这个问题。很多测试报告写得空泛,一张矩阵比我写十句话都有说服力。

4. 执行现场实录:从用例到缺陷报告

4.1 执行准备和前置条件

用例设计好以后,执行阶段反而相对机械,但同样有讲究。首先要确定测试环境。佣金问题如果是个独立接口,可以直接通过接口工具批量调用;如果只能通过UI操作,那就要准备好造数脚本或重复提交数据的操作路径。

我通常会把用例表导出成CSV,然后写个简单的参数化脚本跑一遍。跑之前先确认断言方式:销售额是整数不一定有问题,佣金是浮点数就必须约定误差范围。用JUnit的assertEquals(expected, actual, 0.001),或者Python的pytest.approx都可以。如果不约定误差范围,很多用例会因为0.0000001这种噪声全挂,这时候先别急着报bug,先检查是不是断言写得太苛刻。

4.2 典型缺陷:边界判断写成了<而不是≤

这是佣金问题最经典的bug。需求说“销售额不超过1000元的部分按10%提成”,正确的实现应该是 if (sales <= 1000)。开发很容易写成 if (sales < 1000)。这时候销售额恰好等于1000的用例,预期佣金是100,实际代码会走进第二档,按15%提成,算出150。我这条用例执行完编号TC-BOUND-06,状态Fail,一张清晰缺陷报告:

  • 缺陷标题:销售额等于1000元时,佣金多算50元
  • 复现步骤:输入A=10、B=10、C=10,销售额恰好为1000,点击提交
  • 实际结果:佣金150元
  • 预期结果:佣金100元
  • 严重程度:高,直接影响月度佣金结算正确性

注意这里的“多算50”不是1000的5%吗?1000×15%-1000×10%=50,对。缺陷报告里把业务影响写清楚,开发修复优先级会更高。

4.3 典型缺陷:负数和超上限输入没有被拦截

有的开发只写了佣金计算逻辑,完全没做输入校验。A=-10、B=0、C=0时,销售额=-450,因为-450≤1000,程序给出一条佣金-45元。这个缺陷比边界判断还严重,因为财务系统里出现负佣金意味着用户可能要倒贴钱。报这个bug时,我通常会在描述里附一句“建议在接口层统一做参数校验,而不是依赖前端表单”。

超上限同理,A=71、B=0、C=0时,如果系统没拦截,销售额=3195,照样算出第三档佣金,但业务规则里每种数量上限70本意是防止录入错误数据。这类缺陷往往不是计算错,是逻辑校验漏了。

4.4 典型缺陷:浮点精度引发的金额显示异常

前面提到的1650用例,如果开发用float类型直接存金额,实际结果会在197.49999999999997附近。在自动化断言里用完全相等比较必挂,但如果你会上这条用例并把它标记为Fail,就得小心了。开发可能会说“这只是显示问题,四舍五入一下就好了”,但真实记账系统里,处理顺序不同会导致一分钱差异,所以这笔账要认真算清楚。

我见过一个真实计费系统的案例,某项目组为了省事用double做金额计算,结果每月账单里总有几十条记录比人工核算差一分钱,最后整个对账流程都被拖累。佣金问题这个练习题,就是提前把这种教训以小而完整的形式暴露出来。

4.5 用例执行结果汇总思路

所有用例执行完,我会按“通过/失败/阻塞”分类统计。失败用例统一关联到缺陷;阻塞用例说明前置条件不满足,比如页面还没开发完。一个合格的测试报告不只是数字,还要包含对失败原因的分析:哪些是功能实现错误,哪些是需求歧义,哪些是测试数据本身就设计错了。

5. 从练习到实战:自动化回归和通用资产沉淀

5.1 用参数化用例做快速回归

佣金问题非常适合练自动化测试,因为它逻辑简单、预期好算、用例表结构清晰。给一个Python pytest的示例思路,新手可以直接照着扩展:

import pytest def calc_sales(a, b, c): return 45 * a + 30 * b + 25 * c def calc_commission(s): if s <= 1000: return round(s * 0.10, 2) elif s <= 1800: return round(100 + (s - 1000) * 0.15, 2) else: return round(220 + (s - 1800) * 0.20, 2) @pytest.mark.parametrize("a,b,c,exp_sales,exp_comm", [ (10, 10, 10, 1000, 100.00), (10, 10, 11, 1025, 103.75), (19, 4, 33, 1800, 220.00), (19, 5, 32, 1805, 221.00), ]) def test_commission(a, b, c, exp_sales, exp_comm): s = calc_sales(a, b, c) assert s == exp_sales assert calc_commission(s) == pytest.approx(exp_comm, abs=0.01)

把用例表拆成数据行塞进参数化装饰器,几十条用例几秒钟就能跑完。但注意,自动化脚本的价值是快速回归,不是帮你“发现”测试用例。用例设计没过关,脚本跑得再快也只是把错误预期重复执行一遍。

5.2 从佣金问题迁移到真实计费业务

佣金问题里的分段提成和真实业务中的阶梯计价几乎是同一个模型。电商的满减、快递的运费模板、保险行业的佣金结算、积分兑换规则,底层都是“输入参数→计算金额→输出结果”。从这道练习里沉淀下来的测试资产可以通用:

  • 需求澄清清单:金额精度、边界归属、非法输入处理方式。
  • 用例设计模板:正常功能、单变量边界、组合边界、非法输入、精度校验。
  • 缺陷分析套路:先确认用例预期对不对,再确认开发实现,最后确认需求本身是否清晰。

这套东西我已经在很多真实项目里复用,佣金问题练熟了,遇到复杂一点的阶梯折扣也不慌。区别在于真实业务的规则可能嵌套更多档位、更多维度,但测试设计的底层逻辑不会变。

5.3 这条练习题怎么继续扩展

如果想再进阶一点,还可以给佣金问题加几个需求:按销售员累计月度佣金计算个税、支持批量导入销售数据、并发提交时防止超卖等。每加一个需求,边界条件就多一层,测试设计的复杂度就上来了。我自己带新人时习惯让新人先独立设计一遍,我再拿经典用例做对照讲评。大多数情况下,新人的用例里真正缺的不是“数量够不够”,而是对“销售额1000边界”和“非法输入处理预期”这两个关键点的把握。

我个人做这道题最大的体会是,佣金问题的难点从来不在计算本身,而在测试设计者对“假设”的管理。你假设了数量不会超界、假设了金额永远精确到分、假设了用户不会输入负数,这些假设往往就是线上事故的源头。所以不管练多少遍,我都会提醒自己:预期结果一定要独立手算,边界用例一定要标出可达值,需求没说清的地方一定要落到文档里变成待确认问题。真正好的测试用例不是数量多,而是边界全、预期结果经得起推敲。每次带新人重新做这套练习,我都能从对方的用例里看出他思考问题的方式,这也是这道题能一直流传下来的原因。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进