多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

接口测试用例设计实战:等价类划分法在注册与订单接口中的落地

接口测试用例设计实战:等价类划分法在注册与订单接口中的落地 上个月排查一个线上问题时我发现一个挺扎心的细节一个注册接口的email字段开发只做了“包含”的校验测试这边的接口测试用例也没覆盖到位结果线上有人传了个“123456”注册成功了后续营销系统往这个地址发邮件时消息队列整个堵住。事后复盘问题不出在工具、也不全在开发而出在接口测试用例设计——对这个字段我们根本没把“域名格式”这个等价类别出来。等价类划分法听起来是黑盒测试里的老掉牙内容但放到接口测试场景里它才是真正决定用例质量下限的东西。这几年Postman、Apifox、JMeter这些工具把接口测试的门槛拉得很低随便拖拖点点就能发请求。工具解决的是“怎么测”而等价类划分法解决的是“测什么”。这篇文章我不打算讲理论空话直接拿两个接口当靶子——一个用户注册接口、一个订单查询接口把等价类划分从拆解、编号、设计用例到工具落地完整过一遍。最后再把我这些年踩过的一些坑拿出来说说。无论你是刚转测试的新人还是做接口测试有一阵子、但用例设计一直靠直觉的老手这篇应该都能给你一些能直接拿去用的思路。1. 为什么接口测试最容易在“参数组合”面前失控1.1 接口入参的复杂度远超很多人的直觉在界面上做功能测试时你看到的字段是有限的操作路径也受页面流程约束一个表单里就那么几个输入框测起来相对好掌控。到了接口层情况完全变了。一个稍微核心的业务接口入参动辄七八个、十几个字段每个字段背后都挂着一堆约束类型string、int、boolean、object、长度上限、是否必填、格式要求邮箱、手机号、日期、金额、取值范围、字符集限制、枚举类型……这些规则层层叠加如果不做系统化拆解用例数量马上爆炸。我经常用一个很土但很能说明问题的计算假设一个接口只有3个参数每个参数你准备5种输入1个合法值、几个边界值和非法值穷举组合就是5×5×5125条用例。参数一旦增加到6个每个还是5种输入就是15625条。真实业务接口的字段数通常比6个多得多纯靠穷举组合来保证覆盖既不现实也没必要。等价类划分法就是为了解决这个矛盾而存在的。1.2 靠“感觉”设计用例是最容易漏测的我在评审同事用例的时候发现一个共性大家其实测了不少但测得很不均匀。比如测username一会儿用“abc123”一会儿用“abcd_123”一会儿换个“devops_2024”看起来覆盖了好几条数据但拆开看这些都属于同一个等价类——都是“合法字符集内的正常长度字符串”。真正容易出问题的“以数字开头”“包含空格”“超长”“空字符串”这些非法输入反而经常没人测。这就引出了等价类划分法的第一层价值它逼你把输入域先切分清楚明确哪些情况在行为上是“同一类”每一类里取一个代表值去测做到不重复、不遗漏。接口测试缺的不是发请求的次数而是这种结构化的思考方式。没有等价类的概念你测100条用例也可能是原地打转有了等价类20条用例就能把核心路径和关键异常都圈住。1.3 等价类划分解决的核心矛盾覆盖率与成本等价类划分法源自经典的黑盒测试理论核心思想是把程序的输入域划分成若干个子集同一个子集里的输入程序处理路径是一致的。既然路径一致那就不需要把每个值都测一遍取一个有代表性的值就能覆盖整个子集。放在接口测试里这套逻辑特别适配因为接口的入参本身就是典型的输入域而接口的校验逻辑和处理逻辑通常是一段确定的代码分支。这个方法必须“成对”使用。有效等价类验证的是“系统该收的输入能不能收下、功能是否正确”无效等价类验证的是“系统该拒的输入拒得干不干脆、错误提示是不是清晰”。只测有效类接口的健壮性完全没有保障只测无效类功能本身能不能用你都不知道。举个生活化的例子一筐水果你要验证的其实是“坏果能不能挑出来好果能不能吃”。你不需要把每个苹果都咬一口按“完好的”和“有虫眼的”分成两堆每堆抽查几个就够了。等价类划分法干的就是这件事。2. 等价类划分的底层逻辑有效类、无效类与边界值的关系2.1 “等价”到底是什么意思很多刚接触这个方法的同学容易把“等价”理解成“数值差不多”这就偏了。所谓等价指的是对被测系统来说行为结果是等价的。同一类里的任意一个输入按接口的校验逻辑和业务逻辑会走同一条处理路径返回同一类结果。既然路径相同你测了其中一个代表值就能推断这一类输入的整体表现。拿一个只接受1~100整数的输入框举例。程序逻辑通常是这样if (value 1 value 100) 执行正常逻辑; else 报参数错误。于是1到100这100个整数在程序眼里是完全一样的它们构成一个有效等价类0和101虽然数值不同但都走到“参数错误”分支构成一个无效等价类。你不需要把100个整数全测一遍在1、50、100里选几个代表就够了。但要注意这里选代表值的时候别把边界值当普通值用掉了——1和100虽然属于有效等价类但它们同时又是边界值通常应当留到边界值分析里去专门测。2.2 有效等价类和无效等价类两条腿走路等价类天然分成两大类缺一不可。有效等价类指满足规格说明、程序应该接受并正确处理的输入集合。测它的目的是验证功能正确性这是接口测试的基础盘。无效等价类指不满足规格说明、程序应当拒绝处理的输入集合。测它的目的是验证健壮性和容错能力这才是接口测试真正拉开差距的地方。我在项目里见过太多只乐意测第一类的情况因为“返回200、数据写库成功”看着很有成就感。但线上出故障绝大多数都出在无效输入上有人传了个畸形参数接口没拦住脏数据进了库或者一报错就把堆栈信息原样返回等于给攻击者递刀子。无效等价类验证的是接口的下限而一个接口靠不靠谱恰恰看的是下限而不是上限。2.3 边界值分析和等价类划分不是二选一是先后关系等价类划分法有个天然搭档叫边界值分析法很多初学者会把它们当成两种并列的方法其实不是。正确的关系是先用等价类划分法把输入域分成有效类和无效类再针对每个等价类的边界值做专项补充。因为大量的测试经验表明程序出错的最高发区域就是边界值附近。回到1~100输入框的例子。用等价类划分法你会选出20、50、80这类代表值再选个0和101代表非法类。但如果只测这些你其实没有确认99和100之间、100和101之间的处理是否正确。这时候就需要补边界数据0、1、100、101这四个值单独测验证“等于边界时按有效类处理、越过边界时按无效类处理”。接口层面同理一个长度限制为4~16位的username字段除了各等价类的代表值4位、16位、17位、3位这几个边界数据也必须单独测。我见过接口文档写“4到16位”、开发实现的判断是“16 4”的线上事故这种逻辑问题用代表值测不出来边界值一测就现形。3. 从注册接口说起逐字段拆分等价类的完整过程3.1 拿到接口文档后第一步不是急着发请求设计用例的第一步是把接口文档里的约束条件“挖”干净。我习惯先建一张约束表把每个参数的类型、是否必填、长度范围、格式要求、取值范围、默认值全部列出来。这步看起来很基础但漏掉任何一个约束后续的等价类划分就不完整相当于地基歪了。下面用一个典型的注册接口当实战素材POST /api/v1/user/register Content-Type: application/json请求体示例{ username: abc123, password: pass123, email: userexample.com, age: 25 }约束条件username必填4~16位只能包含字母、数字、下划线不能以数字开头区分大小写。password必填6~20位必须同时包含字母和数字区分大小写。email必填标准邮箱格式形如localdomain.tld。age选填0~150之间的整数。传字符串“25”也会被自动转成数值接受。3.2 username字段的等价类划分演示以username为例约束是“4~16位、字母数字下划线、不能以数字开头”。这个字段可以拆成以下等价类等价类编号类型描述代表值EC01有效字母开头字母数字组合长度正常abc123EC02有效字母开头字母下划线组合abc_123EC03有效边界长度4位a123EC04有效边界长度16位a123456789012345EC05无效空值字段值为nullnullEC06无效空字符串EC07无效长度不足4位a1EC08无效长度超过16位a123456789012345617位EC09无效以数字开头123abcEC10无效包含空格abc 123EC11无效包含特殊字符abc123EC12无效包含中文张三abc细看这个表EC01和EC02从程序处理路径来看大概率是一样的都属于“合法字符集的正常长度”。为什么还要分开列因为有些接口的校验逻辑是分步执行的先查长度再查字符集再查首字符最后查业务唯一性每一步都可能单独抛出不同的错误码。把“字母数字组合”和“带下划线”拆成两个等价类即使现在接口处理路径一致后续规则一旦变化这张表也能快速调整。当然如果项目进度紧合并这两类只取一个代表值也完全可行。用例设计的粒度取决于你对风险的态度没有绝对标准但拆分得越细未来回溯的时候就越清晰。3.3 password、email、age的划分要点password的约束是“6~20位必须同时包含字母和数字”。这个字段的等价类设计重点在“字符组合规则”上等价类编号类型描述代表值EC13有效6位包含字母和数字abc123EC14有效20位包含字母和数字a1b2c3d4e5f6g7h8i9j0EC15无效空值nullEC16无效少于6位a1b2EC17无效超过20位a1b2c3d4e5f6g7h8i9j0k1EC18无效纯字母abcdefgEC19无效纯数字123456EC20无效包含空格abc 123email字段的有效等价类要覆盖常见的标准邮箱格式userexample.com和带点号的用户名user.namemail.com无效等价类要重点覆盖没有、后没有域名、域名没有点、出现两个、前后带空格等情况。注意“前没有内容”也是常见的无效类代表值可以设成“example.com”很多接口在这上面翻过车。age字段因为是选填有一个很容易被忽略的有效等价类——“不传该字段”。同时0和150是边界-1和151是越界1.5是小数abc是非数字字符串。这里还要结合3.1里的约束去确认接口接受字符串“25”吗如果框架自动转int那数字字符串属于有效等价类如果不转那它就是类型错误类。这类“文档说了等于没说”的模糊点测试人员必须找开发确认而不是自己想当然。3.4 从等价类表到可执行用例合并与编号等价类表只是中间产物真正要执行的是测试用例。合并规则我总结为一条“单点失效原则”不同参数的有效等价类可以自由组合用于验证正常流程但无效等价类必须一次只给一个参数喂非法值。原因是如果一条用例同时把username设成null、password设成纯字母响应里返回了错误你根本无法判断这个错误是哪个参数触发的定位问题的成本瞬间翻倍。按照这个原则前面这些等价类可以合并成下面这组用例用例编号场景usernamepasswordemailage预期结果TC01常规注册成功abc123abc123userexample.com18注册成功TC02合法下划线用户名abc_123abc123userexample.com不传注册成功TC03用户名边界长度a123 / a123456789012345abc123userexample.com150注册成功TC04用户名为空nullabc123userexample.com18400提示用户名不能为空TC05用户名太短a1abc123userexample.com18400提示用户名长度不合法TC06用户名以数字开头123abcabc123userexample.com18400提示用户名不能以数字开头TC07密码纯字母abc123abcdefguserexample.com18400提示密码必须包含数字TC08密码纯数字abc123123456userexample.com18400提示密码必须包含字母TC09邮箱格式错误abc123abc123notanemail18400提示邮箱格式不正确TC10年龄超出范围abc123abc123userexample.com151400提示年龄范围不合法TC11年龄传非数字abc123abc123userexample.comabc400提示年龄必须为数字到这里一个注册接口的核心用例就出来了。后面要做的就是把这张表落到实际工具里去执行。4. 进阶订单查询接口里那些单参数划分管不住的情况4.1 单参数划分的盲区参数耦合与业务依赖注册接口的参数之间基本独立单参数等价类划分就能覆盖。但真实业务里很多接口的参数存在耦合关系。最典型的就是时间范围startDate和endDate单独看都是合法日期组合起来可能“开始日期晚于结束日期”这种场景是单参数等价类设计不到的。再比如分页参数pageNum和pageSize单独看都合法但如果pageNum设得很大pageNum乘以pageSize远超数据总量有些接口会返回空列表有些会报“超出最大页码”有些可能触发慢查询甚至拖垮数据库。这些都不是单参数等价类能提前覆盖的必须针对参数间的关联关系补充组合用例。下面用订单查询接口当进阶靶子GET /api/v1/orders?userId1001statusPAIDpageNum1pageSize10startDate2024-01-01endDate2024-01-31约束条件userId必填正整数。status选填枚举值只能是PENDING、PAID、SHIPPED、COMPLETED、CANCELLED五选一。pageNum选填默认1大于等于1的整数。pageSize选填默认101~100的整数。startDate、endDate选填YYYY-MM-DD格式且startDate不能晚于endDate。4.2 枚举值参数与分页参数的特殊处理枚举类型参数的等价类设计和普通字段不一样有效等价类直接对应枚举里的每个值也就是PENDING、PAID、SHIPPED、COMPLETED、CANCELLED这五个各测一次确认每种状态的查询结果符合预期。无效等价类要分两类来看一类是不在枚举范围内的字符串比如把PAID写成PAYED另一类是大小写变化比如paid、Paid。健壮的接口通常把枚举值限定为精确匹配大小写不同应当被拒绝。这个点很多接口都栽过跟头实测中返回的往往是“请求参数不合法”而不是具体的枚举错误但也算符合预期断言按实际返回写就行。分页参数的等价类设计pageSize的有效等价类包括默认值、1、100、普通值比如10无效等价类是0、负数、101、非数字。pageNum的无效等价类包括0、负数、非数字此外还有一个特殊场景pageNum值超出总页数。如果总共只有50条数据、每页10条pageNum10就是合法输入但查询结果为空。这类“合法但无数据”的用例预期结果是明确的——返回200data为空列表但很多人容易漏测。4.3 日期范围这类“状态关联参数”的等价类设计日期参数的等价类拆分重点在格式和关联关系两个维度。格式层面有效等价类是标准YYYY-MM-DD无效等价类要覆盖2024/01/01、20240101、2024-13-01、2024-00-01这些常见错误格式以及非日期字符串。关联关系层面startDate晚于endDate是核心无效等价类两端相等同一天是有效等价类的边界情况。对参数耦合的用例我一般用“正向组合”和“反向组合”两套逻辑来组织。正向组合是把所有参数的合法值组装在一起确认正常查询链路通畅反向组合是每次只破坏一组耦合约束比如只把startDate调晚其他参数全部保持不变这样出错时能精准定位到“日期先后关系”这个约束上。仅破坏一条耦合约束这个原则和前面说的“单点失效原则”是同一个逻辑目的是保证返错原因可回溯。5. 用例落到工具里Postman、Apifox、JMeter的落地姿势5.1 Postman断言脚本与数据驱动Postman是接口调试最常用的工具把等价类用例落进去时最重要的不是把请求发出去而是把断言写好。没有断言的接口测试跑完都不知道对错等于白跑。一个典型的断言脚本长这样pm.test(状态码为400, function () { pm.response.to.have.status(400); }); pm.test(返回错误信息包含预期提示, function () { const jsonData pm.response.json(); pm.expect(jsonData.message).to.include(用户名长度不合法); });把注册接口的11条用例都配上类似的断言后可以用Postman Runner的Data File做数据驱动。把用例数据整理成CSVusername,password,email,age,expected_status,expected_message abc123,abc123,userexample.com,18,200,注册成功 null,abc123,userexample.com,18,400,用户名不能为空 a1,abc123,userexample.com,18,400,用户名长度不合法请求体里通过{{username}}、{{password}}这类模板变量引用CSV字段。有一个坑必须提醒CSV里的null会被当成字符串null传到接口如果接口要求的是真正的JSON null就得在请求发送前用脚本转换一下if (pm.iterationData.get(username) null) { pm.variables.set(username, null); }否则你原本想测“用户名为空”实际测的却是“用户名字符串为null”等价类彻底失效。5.2 Apifox接口调试到用例管理的整合Apifox这类工具比Postman更进一步的地方在于把接口调试、用例管理、自动化测试和Mock整合在了一个平台里。落地的路径很直接先创建接口定义再创建测试场景把接口关联进来每个步骤配置用例数据和断言。断言可以直接选“响应体包含某字段值”不太需要写JavaScript对团队里不太擅长写代码的测试同学更友好。我在Apifox里习惯把每个等价类拆成独立步骤而不是把多条等价类数据合并到一个“大用例”里。理由是Apifox的自动化测试报告会精确到步骤维度哪一条等价类数据失败报告里直接就能看到排错效率比在Postman Runner的日志里翻找高得多。用Apifox做自动化回归时还可以配置“前置脚本”构造测试数据比如注册接口需要唯一用户名可以用时间戳生成避免数据冲突导致误报。5.3 JMeterCSV数据配置与批量执行JMeter虽然常被用来做性能测试但做接口功能用例的批量执行同样顺手。核心配置就两个组件CSV Data Set Config负责读取用例数据HTTP请求里用${username}这类变量引用Response Assertion负责验证返回结果可以配置“文本匹配”来断言错误信息。JMeter数据驱动有个高频雷区——CSV文件编码。很多人用Excel编辑CSV后直接保存保存出来是带BOM的UTF-8格式JMeter读取时第一行第一列会带着一个不可见字符导致首条用例断言失败。保险的做法是用VS Code或纯文本编辑器保存为无BOM的UTF-8。这个坑我踩过不止一次排查时浪费了大量时间后来直接把“无BOM编码”写进了团队的操作规范里。6. 等价类设计最容易翻车的六个细节细节一null、空字符串、字段缺失是三件事。很多接口文档只写“username必填”但实现层面请求体里没有username、username为null、username为空字符串可能返回三种不同的错误码。设计无效等价类时这三种情况必须分开测不要合并。细节二别只看HTTP状态码。有些接口对参数错误返回200但响应体里带一个业务错误码比如code4001。如果你的断言只写了“状态码为200”这类错误会直接被判为通过线上问题就这么溜过去的。正确做法是状态码和业务码双断言。细节三有效等价类的“正确结果”不一定是成功响应。比如查询一个不存在的orderId接口返回200且data为null这其实是合法的业务行为。把“合法但无数据”单独设成一个等价类预期结果写清楚用例就不会误报。细节四大小写、前后空格、转义字符是隐形边界。username区分大小写时ABC和abc是两个值字符串前后的空格有的接口会trim有的不会JSON里的\u0000、\n这类转义字符也可能绕过长度校验。这些“看不见”的输入恰恰是等价类划分最容易漏掉的地方。细节五接口文档没写约束不代表没有约束。很多内部接口的文档极其简陋只写了“userId必填”没写类型、没写范围。这时不能直接开测而是要找开发确认具体的实现约束写进你自己的约束表里。测试用例设计的依据是“实际规则”不是“文档封面”。细节六等价类划分结果要跟着接口演进更新。接口加了新参数、改了长度限制、调整了枚举值等价类表必须同步维护。很多团队的用例库失效不是设计方法有问题而是用例与接口现实脱节了。建议在接口变更的MR里同步更新等价类拆分表和对应用例这是最低成本的维护方式。7. 写在最后这个方法让我养成的习惯按我自己的习惯每设计完一个新接口的用例都会把等价类拆解表单独存档。等接口上线后出了线上问题回头翻这张表十有八九都能发现“当时漏掉了某个等价类”或者“某个参数组合逻辑当时根本没拆出来”。等价类划分法的价值不在于设计当天的效率有多高而在于它能让你随时说出“哪些输入我测过了、哪些没测过”。这种可控性才是接口测试从“做了”到“做透了”的分水岭。最后再分享一个小技巧等价类拆分表本身也是代码评审时的输入。每次接口开发改校验逻辑拿这份表对着代码过一遍很多逻辑漏洞在评审阶段就能看出来根本等不到上线。这个方法用好了不只是测试同学的工具也是开发自测的一把尺子。
返回列表