订单结算测试案例复盘:从需求拆解到浮点精度避坑

📅 发布时间:2026/10/11 10:00:46
订单结算测试案例复盘:从需求拆解到浮点精度避坑
测试案例1编号没什么特别的但我一直记得它。那是一个电商订单结算的用例最初产品给的描述不到三行满100减20、不满99收10元运费、结算金额向下取整到分。看到这种需求老测试基本都能嗅到风险。果然从用例设计到执行再到自动化回归这个测试案例像一根线把需求歧义、浮点精度、数据污染、缺陷定位这些事全串起来了。这篇文章我想拿它做一次完整复盘把每一步怎么想、怎么做、踩了哪些坑都讲清楚。不管你是刚写用例的测试新人还是被订单金额算错过0.01折磨过的开发应该都能找到点共鸣。1. 案例背景为什么要单独拆解“测试案例1”1.1 一句话需求背后的复杂组合“测试案例1”最初来自一个订单结算需求我当时拿到的需求描述大概是这样的用户下单时可以使用满100减20的优惠券订单金额不满99元需要支付10元运费最终支付金额统一向下取整到分。听起来每个字都认识但你真要去写用例会发现到处都是问号。“满100减20”里的“满100”是按商品原价判断还是按优惠券使用后的金额判断“满99包邮”里的门槛和满减门槛是同时判断还是分步判断偏远地区的运费是不是单独一套向下取整到分是最后对总金额取整一次还是对每个计价项分别取整这些如果不在用例设计前确认清楚后面写出来的用例就是空中楼阁。我自己习惯先把需求拆成一张“规则确认表”把每一句话可能的理解都列出来然后去找产品和开发逐条确认。例如需求描述可能的理解1可能的理解2需要确认的问题满100减20商品原价满100优惠后应付金额满100优惠门槛按什么金额判断不满99收10元运费商品原价不满99优惠后金额不满99运费模板是否读取地址向下取整到分最终支付金额取整每个计价项分别取整取整发生在哪个计算阶段这张表看着简单但能帮你把模糊需求逼到墙角。测试设计的第一个动作永远是澄清需求而不是急着打开Excel写用例。1.2 它不是一个用例而是一个测试单元后来我把“测试案例1”定义成一组围绕“订单金额计算”的用例集合而不是孤零零的一条用例。因为一个真正的业务场景往往需要多个步骤配合选商品、进入结算页、使用优惠券、选择地址、提交订单、查看支付金额。每一步都是前置条件每一步都可能影响最终结果。这个测试案例的价值在于它可以作为一个“测试单元”被反复使用。手工测试时我按它逐步执行做接口自动化时我把它的核心步骤转成脚本做回归测试时它又能作为冒烟用例的一部分。它连接了需求、代码、数据和缺陷像一根线把整个流程串了起来。所以这篇文章也适合这几类人看刚入行、写用例只知道“等价类、边界值”两个词的测试新人正在负责订单、支付、结算这类金额相关模块被精度和规则搞到头大的开发以及想做接口自动化但不知道从哪下手的测试工程师。理解了“测试案例1”背后的拆解方式你以后再遇到类似需求会有一种“知道坑在哪”的直觉。2. 测试案例1的设计思路与拆解2.1 先把需求拆成可验证的规则点设计用例前我习惯把需求规则拆成编号每一条都能直接对应断言。针对“测试案例1”最终确认下来的规则是这样规则1满减券门槛按商品原价判断商品原价满100元减20元同一订单只能用一张不可叠加。规则2运费按商品原价是否满99元判断满99元包邮不满99元收10元偏远地区例外固定收15元不参与包邮。规则3最终支付金额 商品原价 - 优惠金额 运费如果优惠后金额小于0按0计算最终金额向下取整到分。注意这里我特别加了“如果优惠后金额小于0按0计算”这来自需求里的隐藏场景。虽然满100减20不会产生负数但系统里还支持无门槛现金券比如新人券无门槛减10元当商品原价只有10元时减完就变成0。支付金额不能为负这是金额类需求的常识但写在文档里的极少。规则一旦变成编号测试点就清晰了。比如规则1我要测“正好满100”、“差1元不满100”、“远超100但只减一次”规则2我要测“正好满99”、“差1分不满99”、“偏远地址”规则3我要测“正常相减”、“优惠后为负”、“取整边界”。这样整个“测试案例1”就不是拍脑袋想出来的而是从规则里长出来的。2.2 正常、边界、异常三类用例怎么落我一般会把“测试案例1”拆成三组正常路径、边界路径、异常路径。正常路径验证主流程能跑通边界路径专挑临界值异常路径则检查系统容错。正常用例先来几条实际可执行的商品原价150元使用满100减20券满99包邮最终支付金额 150 - 20 0 130.00元。商品原价80元不满足满减门槛不满足包邮门槛最终支付金额 80 10 90.00元。商品原价10元使用无门槛减10元券优惠后为0加上运费10元最终支付10.00元。边界用例是最容易出问题的商品原价正好100元满减后80元包邮门槛也满足最终支付80.00元。这是满减门槛的下边界。商品原价正好99元不满减、包邮最终支付99.00元。这是包邮门槛的下边界。商品原价98.99元不包邮加上运费最终支付108.99元。这是包邮门槛的“差一分”场景。异常用例更多是反向检查商品原价100元但优惠券已过期系统应提示券不可用不能直接计算出错误金额。商品状态为已下架即使价格满足条件也应阻断下单。偏远地址的运费模板配置错误导致运费为0最终金额少算15元。这三组用例合在一起才构成一个完整的“测试案例1”。只测正常路径的用例说难听点只是给领导看的花架子。2.3 测试数据准备比想象中更重要写用例的时候很多人只在“测试数据”一栏随手填“商品价格100元”然后就去执行了。实际跑起来才知道数据准备工作量一点也不比写用例小。我当时的做法是先建一个“测试数据清单”把需要用到的数据全部列出来准备项准备值用途测试商品A单价150.00元可售正常满减包邮测试商品B单价99.00元可售包邮边界测试商品C单价98.99元可售不满包邮边界优惠券模板1满100减20有效期覆盖当天满减用例优惠券模板2无门槛减10有效期覆盖当天优惠后为负普通地址非偏远运费10元默认运费偏远地址偏远地区运费15元地区运费例外这些数据为什么一定要单独准备因为测试环境经常被多人共用商品价格可能被改过优惠券状态可能被占用地址模板可能被删掉。如果直接拿别人的脏数据跑用例失败了你都没法判断是系统的问题还是数据的问题。所以我在执行“测试案例1”之前都会先通过造数接口或者SQL脚本重置这批数据确保每一次执行都在同一条件下进行。3. 从设计到执行我踩过的坑与实操记录3.1 环境准备时翻车的细节我第一次执行“测试案例1”时为了省事直接拉了一批线上订单数据的拷贝结果跑第一步就失败了。原因是拷贝过来的优惠券已经过期用例前置校验直接报“券不可用”。我当时第一反应是系统出bug了查了半天才发现是数据环境的问题。那次之后我学乖了每次执行前固定跑一遍环境自检脚本确认四件事用户状态正常、优惠券模板有效、商品可售、运费模板存在。环境自检听起来简单但在多人共用的测试环境里特别有用。有一次我在用例里用了一个“金额正好100元”的商品执行前没检查价格结果另一个同事为了测别的需求把价格改成了120元导致我满减后的预期金额全部对不上。自检脚本就是花一分钟避免后面排查半小时。另外测试环境的时间也需要注意。很多优惠券和活动都有起止时间如果环境时间不对券状态判断就会错。我当时在代码里固定注入一个“当前时间”避免依赖真实系统时间这样用例可复现性会提高很多。3.2 第一个隐藏的bug浮点精度导致金额差0.01真正让我对“测试案例1”印象深刻的是执行过程中发现的一个浮点精度问题。当时有一组用例商品原价149.90元使用满100减20券包邮预期支付金额129.90元。执行时接口返回的金额是129.90000000000003页面显示129.9数据库里存的是129.90。这个现象一出来我立刻意识到是浮点数运算的问题。在Python里复现非常简单# 用float直接算 item_total 149.9 discount 20.0 freight 0.0 pay item_total - discount freight print(pay) # 129.90000000000003原因是二进制浮点数无法精确表示一部分十进制小数。149.9转成二进制浮点后本身就有误差再加上浮点加减运算误差就被放大了。很多开发习惯用float表示金额这恰恰是资金类项目的大忌。正确的做法是使用以“分”为单位的整数或者使用精确十进制运算。比如Python里可以用Decimalfrom decimal import Decimal item_total Decimal(149.90) discount Decimal(20.00) freight Decimal(0.00) pay item_total - discount freight print(pay) # 129.90这个bug最终被定位到后端计算逻辑修复方式是金额字段统一使用Decimal对外接口传参和返回也都统一使用字符串避免精度丢失。这个案例之后我在“测试案例1”里专门加了一条“金额精度专项检查”跑所有订单金额用例时都对比浮点运算结果和Decimal结果确保回归时不会再犯同类问题。3.3 从“用例失败”到“缺陷定位”的流程用例执行失败以后别急着截图提bug先做分层定位。我自己习惯按这个顺序来第一步复现。用同一环境、同一数据再执行一遍确认是否稳定复现。如果第二次通过大概率是脏数据或时序问题。第二步对结果分层。先看接口返回体再看页面显示最后查数据库落库值。通过三层对比基本能锁定问题在哪一层。第三步翻日志。重点看计算服务日志里的入参、出参、中间变量尤其是“取整”发生在哪一步。很多时候金额差一分钱就是取整时机不对。第四步写缺陷描述。不要只写“用例失败实际结果是129.90000000000003”要写明前置条件、操作步骤、预期结果、实际结果、影响范围。我拿一个实际场景说明。当时页面显示129.9接口返回129.90000000000003数据库存129.90。三层不一致说明接口层有精度问题展示层做了格式化数据库写入前又做了round所以最终落的和接口不一致。如果只看页面你根本发现不了问题。这也是我一直强调“页面对了不代表接口对了接口对了不代表库里对了”的原因。4. 测试案例1的扩展自动化回归与结果沉淀4.1 手工用例转自动化回归脚本“测试案例1”跑完手工验证后我没有让它停在测试用例文档里而是直接转成了接口自动化用例。测试环境有一个订单金额试算接口可以传入商品总额、优惠金额、运费然后返回支付金额。我把核心的几条数据参数化用pytest加requests写了一个很小的脚本。import requests from decimal import Decimal BASE_URL http://your-test-env/api/order/calculate def check_amount(item_total, discount, freight, expected): payload { item_total: item_total, discount: discount, freight: freight } resp requests.post(BASE_URL, jsonpayload) assert resp.status_code 200 data resp.json()[data] # 关键金额统一用字符串断言避免精度误差 assert data[pay_amount] Decimal(expected) print(fcase passed: {item_total}-{discount}{freight}{expected})然后通过参数化把所有用例数据塞进去cases [ (150.00, 20.00, 0.00, 130.00), (99.00, 0.00, 0.00, 99.00), (98.99, 0.00, 10.00, 108.99), (100.00, 20.00, 0.00, 80.00), ] for item_total, discount, freight, expected in cases: check_amount(item_total, discount, freight, expected)为什么金额要用字符串传因为如果直接用float传到接口精度问题可能从入口就开始了。而断言的时候我直接用Decimal(expected)比较避免“129.90000000000003”和“129.9”这种浮点比较带来的假失败。这个脚本虽然简单但已经能承担回归任务。每次订单金额相关代码改动我只需要跑一遍这组用例五分钟内就能知道有没有改坏原有逻辑。比每次都手动构造订单快多了。4.2 测试报告结论怎么写才能让开发和产品愿意看执行完“测试案例1”之后总得有个结果沉淀。我见过很多人写测试报告就是一行“用例通过率90%”加一个失败原因截图。这种报告除了证明你做了事没有太多信息量。我后来习惯把报告分成几个固定板块尤其是失败用例必须写清楚四件事现象、定位过程、根因、建议。拿刚才的浮点精度问题举例项目内容用例编号测试案例1-金额精度专项关联需求订单结算规则v1.2执行环境测试环境结论不通过失败现象接口返回129.90000000000003页面显示129.9定位过程对比接口返回和数据库值复现浮点运算误差根因后端使用float计算金额影响范围所有调用订单金额计算接口的功能建议金额统一使用Decimal/分为单位整数存储与计算这样的报告开发拿到就知道改哪里产品拿到就知道影响面测试拿到就知道回归重点。它不只是一个结果还是一次知识传递。4.3 把测试案例沉淀成项目资产测试案例最大的价值不是执行一次而是沉淀下来反复用。我会在用例库里给“测试案例1”打上标签订单金额、满减、运费、精度。下次只要涉及这些模块的迭代我直接把标签拉出来跑一轮回归。同时每条用例都要记录它的版本变化。比如这次因为浮点精度问题我在用例里增加了“金额精度专项”标签之后如果开发把金额计算从“先减后加运费再取整”改成“按项取整再求和”用例的预期结果就要重新评审。用例不是死文档它会随着需求一起演进。我还有一个习惯每次完成一个测试案例都会在用例备注里写一行“执行时踩过的坑”。比如“优惠券必须提前一天创建防止生效延迟”。这些备注可能看起来啰嗦但下次换个人来执行时能帮他少踩一半的坑。5. 常见问题与排查技巧实录5.1 测试数据互相干扰怎么办这是执行“测试案例1”时最常见的痛点。比如用例A创建了一张订单占用了某个优惠券模板用例B再执行时发现同一张券已经不可用导致两个用例一起挂掉。解决办法我总结了三招。第一招用例之间彻底隔离每个用例使用独立用户、独立券模板、独立商品不共用数据。第二招执行前重置每个用例开始前通过造数接口重新发放券、重置订单状态保证前置条件一致。第三招执行后清理用例结束就删除或归档测试订单避免污染后续执行。这里也有一张速查表遇到数据问题先对号入座典型现象可能原因处理方式第二次执行就失败上一条用例改了订单状态用例前置重置数据优惠券不可用被其他用例占用换成独立券模板金额和预期不一致测试商品价格被改锁定测试商品禁止随意改价运费金额不符合预期地址数据被其他用例修改每次重新创建测试地址数据隔离这件事投入产出比极高。与其花两小时排查脏数据不如花二十分钟把用例数据设计成相互隔离的。5.2 用例优先级到底怎么定很多测试新人拿到用例清单第一反应是“每条都很重要都是P0”。如果每条都重要等于每条都不重要。我自己的优先级划分很简单P0是冒烟用例系统核心链路能不能走通比如创建订单、正常支付P1是主流程用例比如满减正常计算、运费正常计算P2是边界和异常用例比如99元包邮边界、券过期场景P3是体验类用例比如文案提示、按钮置灰。“测试案例1”里的普通结算用例属于P0/P1因为支付金额错了影响直接金额精度专项属于P1它会引发0.01误差虽然不阻断功能但对资金类是严重问题99元包邮边界属于P2影响范围有限但值得回归。优先级不是拍脑袋而是按“发生概率”和“影响程度”综合判断。概率高、影响大的就是P0/P1概率低、影响小的就是P2/P3。这样做还有一个好处版本迭代时间紧的时候可以果断砍掉P3而不是纠结到底保留哪条用例。5.3 几个实测有效的避坑技巧最后分享几个从“测试案例1”里沉淀出来的实操技巧每一条都是真金白银踩出来的。第一金额字段能不用浮点就不用浮点。接口传参、断言、数据库存储尽量统一用“分”为单位的整数或者Decimal字符串。这一步到位能帮你规避80%的金额精度问题。第二执行用例前做一次环境自检。确认用户状态、优惠券有效期、商品价格、运费模板四件套。我见过太多用例失败最后发现只是环境时间不对。第三完整保存请求和响应体。不要只在用例通过时打印个“OK”请求体和响应体都存下来。这样即使第二天用例失败你还能对比昨天和今天的差异。第四同时验证页面、接口、数据库三层。页面显示对不代表接口对接口对不代表数据库对。资金类项目最终以数据库落库为准。第五失败之后先怀疑测试数据再怀疑代码。很多“缺陷”其实就是数据没隔离或者环境被其他人改过。别急着提bug先花五分钟确认数据状态。我自己在复盘“测试案例1”时最深的体会就是一个测试案例能不能让人放心不在于编号多漂亮而在于它是否把规则讲清楚、把数据准备好、把结果沉淀住。哪怕只是记住“金额别用float”这一条以后再碰订单计算类需求你都会比之前多一分底气。