为什么要进行正交请求参数测试
以后台服务的视角看,客户端往往在系统边界之外,不受系统控制。系统收到怎样的请求取决于客户端发出怎样的请求。例如,支付宝券平台,她的服务面向阿里全集团,淘宝、天猫、航旅、彩票等等,都是她的客户端。
客户端可以发出所有可能的正常请求和非法请求。因此,系统不应当对外部请求做任何限定性(如合法性)的假设。如果因为收到客户端发出的非法请求或某种特殊正常请求,而导致系统不能正常工作(严重的如停止服务等)——即使是客户端的错误,系统的容错性、健壮性也是不可接受的。
这是一个浅显的道理,很好理解:只有更强大的容错性和健壮性,才能为客户提供更高质量的服务;另一方面,即使客户端的错误也不应当成为我们犯错的理由,或者说,不能用别人的错误来折磨自己。
为确保系统的容错性、健壮性,就必须对系统进行完备的正交请求参数测试。正交请求参数测试是一项重要的测试内容,是从源头上确保系统正确响应所有正常请求,并且不受任何非法请求影响的重要的方法。
什么是正交参数
一个请求对象往往具有多个参数。如果把每个参数看作一个维度,且每个维度都可以自由变化(即不受任何其他维度的约束),则所有参数的每 2 个参数都是正交的。
OrthotropicRequestTestBase 把请求的每个参数作为一个维度。一个请求有多少个参数就有多少个维度。每个维度可以自由变化,选取所有可能的值,而不受其他维度的约束。显然,正交参数构成的请求对象,就一定能够覆盖所有可能的合法的请求、所有可能的非法的请求。
智能正交请求自动化测试实践
1. 背景
券平台的高可用性要求:以任何合法入参、非法入参及其组合来调用平台的服务接口(如创建券产品服务接口 VcpVoucherProductFacade#createVoucherProduct(VcpCreateVoucherProductRequest request)),都必须确保平台完全不受影响,并且进行恰当的处理。这就必须针对每个对外服务接口,以所有可能的 request 入参进行无遗漏的测试。如 request 为 null,或者 request 不为 null 但它的 productCode 属性为 null,等等。
券平台对外提供券产品创建服务接口是: VcpVoucherProductFacade#createVoucherProduct(VcpCreateVoucherProductRequest request)。如下图所示:
2. 编写测试类和测试方法
编写完成的测试类和测试方法如下图所示:
-
编写测试类:从 OrthotropicRequestTestBase 派生测试类 InvalidCreateRequestFuncExceptionTest 派生自 OrthotropicRequestTestBase。基类 OrthotropicRequestTestBase 将自动解析非法请求配置。所以你的测试类无须再做配置解析工作。另外,OrthotropicRequestTestBase 将自动为你生成 null request 测试用例。你无须再关注整个 request 入参为 null 的非法情形。
-
实现测试方法: 使用 @Test(dataProvider=”OrthotropicRequestProvider”) 注解。
-
测试方法形如: ```
@Test(dataProvider = “OrthotropicRequestProvider”)
public void testXxx(OrthotropicRequest
orthotropicRequest)
其中,@Test(dataProvider=”OrthotropicRequestProvider”) 注解表明:ATS 测试框架(拦截定制了 TestNG 测试方法调用过程)将向你的测试方法传递一个 OrthotropicRequest 入参。 OrthotropicRequest 支持泛型,支持各类具体的请求类,如 VcpUpdateVoucherProductRequest 等,这样测试其他场景和其他类型的请求时可以复用,从而降低同类测试的成本。 ### 3. 生成测试驱动和正交参数配置 第一次运行测试时,OrthotropicRequestTestBase 会智能地为你生成定义好格式的空的测试 驱动文件和非法值配置文件。 1\) 自动创建测试驱动和正交参数配置提示 ![](https://static001.infoq.cn/resource/image/c2/7f/c2f30c3c19bcda8b1afa9d12ebdd087f.jpg) OrthotropicRequestTestBase 已为你自动创建测试驱动和正交参数配置,内容仅包含格式定义和必要信息,需要你进一步完善配置。自动创建的配置中下图所示: ![](https://static001.infoq.cn/resource/image/f8/ff/f8793edb0d76e98fb5cf67513d61a9ff.png) 2\) 测试驱动的内容 ![](https://static001.infoq.cn/resource/image/c6/76/c6c0d7e5d16fc9e5067a52079e265076.png) 3\) 正交参数配置的内容 ![](https://static001.infoq.cn/resource/image/e6/af/e660c3f9e9658243035f2f682a992aaf.png) ### 4. 测试驱动——数据配置入口 采用符合 ATS 测试框架约定(约定的路径、约定的测试驱动文件名)的测试驱动,以平滑学习曲线并降低应用成本:如 testres/funcExp/InvalidCreateRequest/InvalidCreateRequestFuncExceptionTest.testInvalidCreateRequest.csv,主要内容如下图所示: ![](https://static001.infoq.cn/resource/image/15/c8/15b74a026cfc34154fac2d1f2074d0c8.png) - orthotropicRequestConfigFile:指定非法券产品创建请求配置(将由 OrthotropicRequestTestBase 自动解析)。 ### 5. 券产品创建请求的非法值的配置 orthotropicCreateRequestConfig.csv ![](https://static001.infoq.cn/resource/image/09/91/09d28d4c9718f8ee2bd5c1336f792e91.png) - 为请求的每个属性配置所有可能的非法值 券产品创建请求 VcpCreateVoucherProductRequest 具有 11 个属性。原则上说,要逐条配置 11 个属性的非法值。另外此处简要提及,OrthotropicRequestTestBase 提供按类型配置非法值的低成本配置方式。例如:productCode 和 assetsCode 都是“必填的、长度不超过 32 的字符串”,那么,可以不逐条配置 2 条属性,取而代之针对 java.lang.String 类型配置非法值,不再详述。 - orthotropicValueList——属性非法值列表 属性非法值列表非常关键,它由一组枚举的非法值构成,必须能够覆盖所有可能的非法值。举几个具有代表性的例子: - pid 是“必填的、长度不超过 16 的字符串”。那么,pid 所有可能的非法值类型为: - 不填,即 null 或空串 - 长度超过 16 的字符串,如“123456789012345678901234567” 综合起来如上图中 pid 属性所示。 - useScene。必填的、存储要求为“长度不超过 8 的字符串”。业务对 useScene 的约束为“订单券(‘ORDER’)或支付券(‘PAY’)”。那么,useScene 所有可能为非法值的情形为: - 不填,即 null 或空串。 - 长度超过 8 的字符串。如“123456789”。 - 长度不超过 8,但字符串内容不为“ORDER”、“PAY”。如“INV”。 综合起来如上图中 useScene 属性所示。 - fundAccounts,保证金账号列表。选填项,即至少应当具有一个保证金账号;存储要求整个列表是“长度不超过 1024 的字符串”。所以,fundAccounts 所有可能的非法值类型为: - 不填,即 null 或空列表。 - 整个列表的长度超过 1024 个字符。如,列表包含 2 个保证金账号,每个账号的长度为 513 个字符,从而整个列表的长度将超过 1024。 综合起来如上图中 fundAccounts 属性所示。 ### 6. 验证测试结果 ![](https://static001.infoq.cn/resource/image/03/3b/03fa9df65307e6c3b349d4c83b99463b.png) 从测试报告可以很清晰的看到: 你只编写一个测试方法,但却执行了 53 个测试用例,并且能够从理论上根据加法原理判断完全覆盖了所有请求非法的情形。 思考题 对于一份给定的请求非法值配置,如何计算出将执行的测试用例的个数?这个问题很有思考价值,因为搞清楚这个问题,我们才能判断是否完全覆盖了所有的请求非法的情形! - 每个测试用例测试了哪个非法值情形。上图一望而知,不再赘述。 成本收益分析 以券平台创建券产品服务来说,成本收益分析如下表所示: 基于 OrthotropicRequestTestBase 传统测试方法 成本收益分析 一个测试方法,执行 53 个测试用例 53 个测试方法 测试方法开发成本为传统方法的1.9%。 逐个属性配置非法值:15 条配置 逐个非法值罗列:53 条组合配置甚至 53 个配置文件! 配置成本为传统测试方法的28%。 测试结果清晰,便于验证是否完全覆盖所有非法情形 容易遗漏个别非法情形,并且很难从数量上予以分析和发现 / ## 正交请求参数测试的简要分析 ### 典型问题 当客户端以特定请求参数调用服务时,系统需要对请求参数进行完备的校验,以便确定请求的合法性,同时确保系统的正常工作完全不受非法请求的干扰。 为了便于说明问题,选取券平台创建券产品作为简单示例予以说明。券平台对外提供创建券产品服务接口,客户端调用此接口创建券产品。
VcpVoucherProductFacade#createVoucherProduct
(VcpCreateVoucherProductRequest request)
当券平台收到请求后,首先对请求进行合法性校验,然后创建券产品 VpProduct 对象并将它存储到数据库 vp\_product 数据表中。 ### 服务的定义 ![](https://static001.infoq.cn/resource/image/27/ed/273f431d593782e91bf0ccf59464cded.png) ### 接口
VcpVoucherProductFacade#createVoucherProduct(VcpCreateVoucherProductRequest request)
### 接口参数 请求由 VcpCreateVoucherProductRequest 定义。它有 11 个属性(如下图所示。不计其中的 serialVersionUID),除资金账号 fundAccounts 的值是字符串列表 List<String> 外,其余属性的值均为字符串 String。 ![](https://static001.infoq.cn/resource/image/6b/2c/6bcf7cddb5a81faacf514602fa4b182c.png) 之所以属性值多定义为字符串 String,是为了将系统与客户端解藕,系统升级(如资金计算类型 calcType 属性,由现在的“定额”FIXED、“不定额”FLOAT,扩展一种新的类型假设为“按比例”RATIO)时,无须捆绑客户端强制升级。 ### 存储 之所以要把存储的定义予以说明,是因为对于请求合法性校验来说,存储定义往往是请求合法性重要的和主要的依据。券产品领域对象 VpProduct 最终将存储到 vp\_product 数据表中,如下图所示。 {1} ![](https://static001.infoq.cn/resource/image/f4/c1/f49cc13319c501ca25969a2f8e9209c1.png) {1} ### 定义参数的合法性 {1} 一个参数是否合法,取决于它的值是否在其值域内。在值域内则合法,否则就是非法的。所以,要定义参数的合法性,关键是要定义它的值域。 {1} 定义参数的值域 {1} 决定参数值域的因素,一般包括存储、业务约束等。以券产品创建请求的 pid、useScene 和 fundAccounts 参数为例分析如下: {1} 请求参数 {1} 存储约束 {1} 业务约束 {1} 值域 {1} pid {1} - nullable:NO。所以 pid 为必填。 - varcher(16)。所以 pid 字符串长度不能超过 16。 无 {1} 长度不超 16 的、非空的字符串。 {1} useScene {1} - nullable:NO。所以 pid 为必填。 业务定义 useScene 为 2 种: {1} 2 个字符串构成的集合:{“ORDER”, {1} - varchar(8)。所以 useScene 字符串长度不能超过 8。 - “ORDER”(订单券) - “PAY”(支付券)两种。 “PAY”} {1} fundAccounts {1} - nullable:NO。所以 fundAccounts 为必填。 - varchar(1024)。所以 fundAccounts 字符串长度不能超过 1024。 业务定义 fundAccounts 为保证金账号列表。 {1} 一个或多个保证金账号构成的列表,并且整个列表字符串最终的长度不能超过 1024。 {1} ## 请求合法性测试用例设计 {1} 合法性测试的目的,是要确保所有的非法的情形下都能正确地工作。合法性用例设计的关键,是要发挥创造力,找出所有的非法值。注意,是非法值而不是合法值。如果请求合法性测试是黄药师的碧海潮生曲,那么,找出所有非法值就是郭靖的鼓点——要把每一个点都敲在“反节奏”上。背后的逻辑是:扛的住所有“反节奏”的鼓点方显碧海潮生曲的功力!才可能是 {1} 靠谱的系统服务。 {1} 在值域定义清楚以后,非法请求的测试用例设计就很容易了。仍以券产品创建请求的 pid、useScene 和 fundAccounts 参数为例分析如下: {1} 请求参数 {1} 存储约束 {1} 业务约束 {1} 值域 {1} 非法值用例 {1} pid {1} - nullable:NO。所以 pid 为必填。 - varcher(16)。所以 pid 字符串长度不能超过 16。 无 {1} 长度不超 16 的、非空的字符串。 {1} - null - 空字符串 - 非空串且长度为 17 useScene {1} - nullable:NO。所以 pid 为必填。 - varchar(8)。所以 useScene 字符串长度不能超过 8。 业务定 useScene 为 2 种: {1} - “ORDER”(订单券) - “PAY”(支付券)两种。 2 个字符串构成的集合:{“ORDER”, “PAY”} {1} - null - 空串 - 非空串且长度为 9 - 非空串且值为“INV” fundAccounts {1} - nullable:NO。所以 fundAccounts 为必填。 - varchar(1024)。所以 fundAccounts 字符串长度不能超过 1024。 业务定义 fundAccounts 为保证金账号列表。 {1} 一个或多个保证金账号构成的列表,并且整个列表字符串最终的长度不能超过 1024。 {1} - null - 空列表 Collections.EMPT Y\_LIST 对象 - 2 个保证金账号构成的列表,并且每个 - 保证金账号字符串的长度均为 513 设计出正交的非法值用例,就可以将它们配置到非法值列表中,利用 OrthotropicRequestTestBase 进行自动化测试及回归了。 ## 常见扩展性问题 智能正交请求参数测试方法在扩展性、高效性方面具有显著优势。比如,当系统发生重构时,如 DB 重构增删请求参数,或者根据测试覆盖率而增加相应参数值组合,智能正交请求参数测试方法易于扩展,可以随系统进行低成本的重构,并迅速得到回归测试的结果。 ![](https://static001.infoq.cn/resource/image/e5/56/e5b81acf4fee2d137eb951298b16ec56.jpg) ## 结论 综合以上,地毯式请求合法性自动化测试——OrthotropicRequestTestBase,具有如下优势: 1. 遵从 ATS 框架约定因而易于理解和使用; 2. 高度可复用的 OrthotropicRequestTestBase 及其 @Test(dataProvider=”OrthotropicRequestProvider”) 注解,大幅降低测试的开发成本; 3. 非法值配置简单易用不易出错且易于提供工具支持; 4. 测试结果清晰,便于判断是否完全覆盖所有可能的非法的或合法的请求。 5. 易于扩展。当 DB 或领域模型重构时,测试用例易于同步演化。 它在工程实践中具有简化测试工作,事半功倍的效果。也在一定程度上把 boring 乏昧的测试工作变得轻松有趣,快乐工作。 - - - - - - 感谢 [侯伯薇](http://www.infoq.com/cn/author/%E4%BE%AF%E4%BC%AF%E8%96%87) 对本文的审校。 给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 [editors@cn.infoq.com](mailto:editors@cn.infoq.com)。也欢迎大家通过新浪微博([@InfoQ](http://www.weibo.com/infoqchina),[@丁晓昀](http://weibo.com/u/1451714913)),微信(微信号:[InfoQChina](http://weixin.sogou.com/gzh?openid=oIWsFt0HnZ93MfLi3pW2ggVJFRxY))关注我们,并与我们的编辑和其他读者朋友交流。
评论