接口测试 **复杂业务流**末端的接口前置数据应该如何准备?

Helen-Shen · 2020年04月29日 · 最后由 Helen-Shen 回复于 2020年05月07日 · 4910 次阅读

做接口测试有个难点一直没有找到很好的解决方案,就是针对业务流程非复杂的末端的接口测试逻辑时,依赖的数据应该通过什么方式准备。
例如:
有一个单据,需要经过【处理 1】-【处理 7】后,操作【处理 8】进行完结这笔单据,并且单据的处理 1-处理 7 操作从前端操作上是不可跨越的。
我需要测试【处理 8】这个接口的业务逻辑的时候,需要单据已经完成了【处理 1】-【处理 7】的操作,且经过【处理 8】后的单据是不可逆,不能再利用的。
问题:
那么我的测试【处理 8】这个接口的时候,数据准备应该如何?
试过的方案:
方案 1
调用前置接口【处理 1】-【处理 7】,但是流程过于长,前置接口有出问题的风险,且逻辑分支比较多,通过此方法造数据可能造数据的调用代码远远大于实际测试【处理 8】接口的数据。
方案 2
先页面造好数据,进行数据库 dump,由于外部因素,开发这边不能给出一个接口涉及掉的表结构等等,所以整个数据库 dump,但是存在各个业务可能同时由不同的人触发,整个库 dump 就不能多线程执行用例,不灵活,而每个业务接口一个库也不现实。

求助,有什么好的方案?

追加
1、说明一下另一个不太愿意调用接口去造数据的原因是,现在业务中例如单据这种都是要求的 id,包括单据中的一些属性值都是需要传值 id,然后和公司账号相关联的,还考虑到环境的问题,例如我测试环境和灰度环境,数据库是两套,所以一套入参不能满足两个环境运行,可能我还是想要脱离这个限制吧,让这个接口测试代码是可复用的,而不是一旦换了环境或者数据库因为某种原因重置了,一些前置的操作还需要手动去执行。
2、不知道大家现在服务架构是如何的,我们现在是在切换到 vue+springboot 的模式进行中,会有一个 service 和 controller,我的理解上,要做接口测试,应该是针对 controller 层的,但是实际要求我做 service 层的接口测试,会有点迷茫吧,就是一个 service 接口可能支持了好多 controller 的,实际上 service 接口的入参真的有点庞大了,让我在对入参组合的测试上非常的懵,不知道界限是什么。

共收到 30 条回复 时间 点赞

就方案一就行。
处理 1-7 的代码不是现成的么。。。你挨个调一下就行。而且接口层,怕什么代码多。。。
“逻辑分支多”——该覆盖的还不是要覆盖
“前置接口有出问题的风险”——我巴不得他出问题,出问题才是好事情啊。。。

我们 crm 系统也遇到这种问题,以前构造一个测试数据需要 5 分钟左右,因为需要依赖前端的一些逻辑才能往下走。去年我们推动研发做前后端接口分离,把接口标准化,然后我们测试的同学通过业务梳理、数据库字段梳理,硬是实现通过 API 来构造数据,现在一个涉及 5、6 个系统关联的数据准备只需要 10 多秒,执行一些关联 API case 即可,当时我让他们去弄的时候,他们也告诉我不可能

仅楼主可见
Ouroboros 回复

逻辑分支多指的是可能前面一顿操作猛如虎,【处理 8】接口的入参有十几种情况,那么在我看来其实就是十几种不同入参,想要断层在这个结点上,而不是为了造数据【处理 1】-【处理 7】一顿调用
前置接口能测试问题当然是好事情,侧重不一样吧,可能我局限于仅仅关注单个接口【处理 8】了,那么在我看来在【处理 1】-【处理 7】中会出现的问题不是我的测【处理 8】这个接口的重点吧

simple 回复

推动开发给一些东西可能也是我的难处吧,业务迭代下,上层的最终目标还是要求你功能快点开发,测试先功能测试测好,所以有些接口和数据库表上的输出一直就是比较难推动给出吧 o(╥﹏╥) o

Helen-Shen 回复

说到底还看当下是不是非解决不可的事吧

simple 回复

说实话,从公司(上层)的角度,只关心卖的多就好,不会关注你测试如何如何,只会就是口头上支持你做,但是现在 1:7 的比例和需求不明确还要耗费不少时间细化需求,在公司测试层面,真的感受不到这个事情的重要,但是作为个人吧,还是想要提升自己,想要是这种复杂程度的也能有做起来一点,有可靠性的话,那么会是我测试上的一大步。

其实每个接口多少都有这个问题,在复用性的角度上暂时我也没有很好的思路

Helen-Shen 回复

那是挺困难的,测开比例那么高的情况下,基本上被业务淹没了

Helen-Shen 回复

个人经验:调 1-7 最靠谱,哪怕 1-7 逻辑变了,参数没变,你也就调个接口而已,不影响。我试过自己根据前置逻辑数据库直接生成数据,但是有时候更新不及时就一大堆错。

另外你 7 咋生成的前置数据。。。如果有线性关联的话,用 7 的前置 +7 不就生成了 8 的前置数据了么。。。

还是方案一靠谱一点,把 1-8 步的脚本写完,整个步骤就可以随便封装了
可以参考下茹炳晟写的测试数据准备的文章,开阔下思路: https://time.geekbang.org/column/article/40006

调接口造数据最实在,不用管其他问题.遇到个数据表结构没设计好的经常变更的,直接造数据简直要命

那么我的测试【处理 8】这个接口的时候,数据准备应该如何?

个人建议用方案一,从 1-7 的 api 一个个造过来,原因嘛,主要是方案二很难协作,且很明显对外的接口改动频率会比对内的数据库结构改动频率要低和容易察觉。成本方面,其实一次性做 8 个接口的 http 调用函数,如果接口文档明确,或者基于抓包的实际报文直接写调用(注意是调用而非用例),应该全时间投入的话 1 天内的工作量?

1、说明一下另一个不太愿意调用接口去造数据的原因是,现在业务中例如单据这种都是要求的 id,包括单据中的一些属性值都是需要传值 id,然后和公司账号相关联的,还考虑到环境的问题,例如我测试环境和灰度环境,数据库是两套,所以一套入参不能满足两个环境运行,可能我还是想要脱离这个限制吧,让这个接口测试代码是可复用的,而不是一旦换了环境或者数据库因为某种原因重置了,一些前置的操作还需要手动去执行。

不大明白为何和环境有关就会影响用接口造数据?

我举个场景,比如第一个接口是创建订单,第二个接口是修改订单。第一个接口会插入记录创造 id ,然后第二个会需要根据创建的订单去请求,那只需要在第一个接口调用成功和第二个接口开始调用之间,增加一个数据库查询的动作就可以了。至于如何查,可以考虑根据订单的创建时间、订单内容之类的去查询,只要订单内容有一定的随机性(比如加上一些日期时间戳)就好

而多环境,本质上是配置的差异(后端服务的 url、数据库地址等),这些只需要把这些抽离成配置项或者全局变量,切换时调整这些值就好,接口调用本身是可以复用的。我们现在内部也是这么做,框架集成了 spring boot config 特性,通过 profile 切换不同配置文件,进而切换环境。

2、不知道大家现在服务架构是如何的,我们现在是在切换到 vue+springboot 的模式进行中,会有一个 service 和 controller,我的理解上,要做接口测试,应该是针对 controller 层的,但是实际要求我做 service 层的接口测试,会有点迷茫吧,就是一个 service 接口可能支持了好多 controller 的,实际上 service 接口的入参真的有点庞大了,让我在对入参组合的测试上非常的懵,不知道界限是什么。

service 一般不会直接暴露为外部调用的 http 接口吧?不大理解你这里接口测试的范畴是什么。一般 mvc 结构里,service 应该在 controller 和 dao 之间,用于做比较复杂的业务逻辑,避免 controller 过重。如果针对这一层做测试,个人理解应该是属于单元测试范畴了,需要把 dao 通过 mock 等手段控制返回,然后模拟各种可能的场景组合(查到数据、查不到数据、查到但值为 null 等)。

自动化的难点就是这个,需要测试接口 8,结果需要把 1-7 的接口都调用一次。然后各种环境问题,各种团队合作的问题,各种不稳定的问题。不是自动化本身有多难,而是你需要把周边的各种事情安排的妥妥帖帖。生活是真艰难。

陈恒捷 回复

你好,我看到你的回复这里有些疑问,"基于抓包直接写调用,调用而非用例"方便举例解释下吗?
如果接口文档明确,或者基于抓包的实际报文直接写调用(注意是调用而非用例),应该全时间投入的话 1 天内的工作量?

monster 回复

比如 jmeter 支持把自己设置为 http proxy 后,自动录制经过的调用为 http sampler ,然后调整改动下后可以变为接口调用的封装。
又或者把你的抓包记录导出为 har 格式,然后用 httprunner 生成对应的调用 yaml 用例,同样把需要变化的入参变为变量传入,也可以成为接口调用的封装。

可能数据有点太乐观,1 天我这里指的是编写用例的时间,具体调通 8 个接口并可以连起来,如果遇到阻碍或者疑难杂症(比如环境有问题、用的是非 http 接口、数据带有加解密之类的),估计会需要更长时间。

确实有两种实现方案,如你所说。
第一种,抽象 1-7 步的步骤,然后再测试第 8 步的时候,调用 1-7 步。
第二种,mock 出 1-7 步的中间数据,而后直接进行第 8 步的测试。

第一种显然是更合理的,因为测试应该尽量不 mock,你可能漏掉 1-7 步的测试能发现的 bug。如果这种做法从前端太慢,或者前端操作不稳定容易出错,合理的做法应该是采用接口自动化。

dump 数据库的方案中,你的思路很奇怪,或者是我没有理解你的逻辑。
理论上应该是找一个干净的数据库,然后你进行 1-7 步的过程,然后 dump 数据库,该数据库为标准数据准备的数据库;这样你甚至可以为每个测试用例的数据准备,定下固定的业务 id。我没理解这个和并发有什么关系,以及为什么要研发给接口。

陈恒捷 回复

谢谢解答,有做过用 fiddlerScript 转存抓包的 http 报文到 txt 文档,然后用 python 解析成一个个标准接口发起请求,但是没去解决现有用例对接口的调用,正好参考下 httprunner 的处理方式😀

陈恒捷 回复

关于 1:例如我调用新增订单的时候,我需要选择一些额外的系统信息(必填项),例如用户、店铺、商品等等,那么如果这些是前置造好的话,那么每个环节新增用户生成的 id 什么的都不一样,所以才会不好配置,因为这些属性太多了;但是如果我为了新增订单,每次再去新增用户、店铺、用户等信息那么我觉得链路过长了。
关于 2:service 是一般不暴露的,所以要在部署环境调用,现在的情景是我们 90% 的代码是没有抽去 controller 这一层的。我之前一直认为接口测试做到 controller 层的请求的,而不是直接去调用 service 层,因为对于一些业务流程,相当于我又组装了部分的 controller 逻辑,但是上级一直认为 service 也是方法,可以在同环境调用,那么也能做成接口测试,而实际上,过于复杂的 bean 之类的入参,让我在设计用例的时候,一直很矛盾。关于 service 层是用单元测试覆盖,这一点我完全认同。

cool 回复

嗯嗯,mark,学习中,会吸收一下新的思想,感觉会有收获。

spirit9111 回复

嗯嗯,这点之前倒是没有太关注,表的更新字段问题导致的不稳定性,不过我个人认为,造数据例如新增,指定了列名的话,不去更改表的列名,只是新增列属性的话还是影响较小的,当然新增的属性字段是关键必填的话,那么确实很麻烦了。

simonpatrick 回复

是的,这个问题已困扰我很久了,难点在前置的数据怎么准备,如何做到数据闭环,可重复利用,且能并发时相互解耦,每个环境不需要太多的人为造数据、改数据,而是真正的一些通用提取。尝试过一些方案,没有很好的效果吧。

陈恒捷 回复

另外还有个痛点是,现在系统相当于是三种不同架构模式的,就是①部分界面采用的是 vue+springboot(该部分有 controller 层),②部分界面为 dorado+springboot(该部分无 controller 只有 service 层),③最老的一部分是 dorado+dubbo,而新增订单还是用后面一种方式编写的,暂时第③部分职能调用 dorado 转接过的接口,即 xml 格式,有很多框架生成的属性,而现在让我测的是第②部分,也很头大,因为趋势是最终会到方案①,所以我才会想数据和接口隔离开吧,不要通过接口造数据不然一套接口测试工程也比较的乱,因为针对不同接口调用方式什么的都不同。o(╥﹏╥) o

皮皮哥 回复

dump 数据库的问题是在于这种场景:
最终接口测试用例的组织形式不会是每次所有的都走一遍,因为针对不同测试阶段可能会需要选择业务模块等,然后执行部分测试用例,或者是错误重试、调试等等。那么就可能存在 A 同学和 B 同学同时都要执行模块 01 的用例,或者是 A 同学和 B 同学需要分别执行模块 01 和模块 02,那么我理解数据库将之前 dump 出来的数据再重置的时间节点在开始执行接口测试用例之前,或者是执行完成接口测试用例之后,那么如果接口用例还在执行的时候,数据触发了重置,那么肯定会对结果产生影响。因为现在是整个数据库重置,那么可能后面的的一个接口 api01 是更新一张表的 status 字段,更新是成功的,但是这个时候数据库被重置了,那么又将字段重置回到原来值,那么就无法得出正确的结果。

当然,如果说是每次执行都是讲所有的接口测试用例都跑一边,或者一旦有人触发执行了部分用例时,不能发起其他执行请求或者是请求等待,那么不会有什么问题。但是数据库表还是颇多的,一次重置约 3min 左右。

Helen-Shen 回复

我理解了,不过有几点要说一下:
1 一般只在一组自动化测试之前,进行一次初始化数据库动作。所以这次自动化过程中,每一个 case(需要中间数据的),都会被准备一个数据;换句话来说,case 1 和 2,即使需要的数据完全相同,也要准备两份,因为这个中间数据用完就没有了,再用要重新生成。(这也是为什么 dump 数据库不是好的方案)

2 看你说的类似于手工测试要用?手工测试要用的话,也是建议不要用 dump 数据库的办法,还是走正常的准备。只不过,你可以把数据准备的过程脚本化掉,然后教会手工的同学怎么执行。这样他们在手动跑 case 前,可以用自动过程准备数据,其实很方便。

Helen-Shen 回复

关于 1:例如我调用新增订单的时候,我需要选择一些额外的系统信息(必填项),例如用户、店铺、商品等等,那么如果这些是前置造好的话,那么每个环节新增用户生成的 id 什么的都不一样,所以才会不好配置,因为这些属性太多了;但是如果我为了新增订单,每次再去新增用户、店铺、用户等信息那么我觉得链路过长了。

我理解你的目标应该是造数据给第八个接口用?如果是,那先保障第八个接口的需要,如果第八个接口可以使用固定的用户、店铺等信息,那就在你的接口调用里直接把这些 id 做成配置项传入即可。id 管理由人来做。到后面有需要那就把配置项改为不是人写上去,而是程序自动生成和记录即可。

另外还有个痛点是,现在系统相当于是三种不同架构模式的...

这个是老系统持续重构的常见问题,严格不是技术问题,而是取舍。最低成本当然是统一架构后再直接基于新架构写接口用例,但这样价值也最低,因为重构过程中用不上。如果重构也希望有自动化协助,那就要增加成本(新老架构各写一套用例)。

至于接口调用实际使用协议的差异,其实本质上所有协议都是数据传输加接收。框架设计得好数据应该是抽象的模型(比如 model 层),具体协议可以直接基于模型写生成方法(如 json、xml、yaml 等),请求发送也是类似,统一命名为 send 方法,使用者只需要用这个方法即可。至于实际用的是 http 还是别的协议,由负责维护这个接口的同学写具体实现即可。我理解并不是不能做,而是这么做框架设计会变得复杂一些,所以需要像上面说的,衡量投入产出比。

皮皮哥 回复

关于第一点我完全赞同,所以本身也认为不是一个很好的方式,也尝试过 dump 数据库重置的操作,只有我一个人一个数据库时还能相对好的隔离操作,但是一旦想要其他人介入,那么数据隔离设计上会比较繁琐,因为不一定能在使用时很好的用约定来约束数据隔离。
关于第二点,其实也不算是手工测试要使用,只是现在我们一个大的产品线是按照功能模块再次分组的,那么我的理解是一个产品一个接口测试项目的,然后功能模块按照包区分,那么最终在未到合并主干回归的场景时,是会需要分模块自己执行下各自模块的 api 接口测试用例,因为相对已经投入使用的 gui 是这样的,当然 api 的执行效率要比较 gui 高的多,但是假设在 api 的不断累积后,这个在我们公司这个产品是一个比较明朗的需求,所以会想要前置的设计好解决办法,如果是每次都执行一次所有的接口测试用例的话,那么 dump 的可行性会稍微高那么一点。

陈恒捷 回复

第一点,之前在另一个项目中试写过几个功能的接口测试用例给她们的测试使用,形式是她们给出要测试的业务流程和基本数据,我来实现,她们的诉求是造数据能不能只给出看到 code 值(页面上实际展示的编码),id 界面上看不到,也不知道是什么,所以采取的方式是我会通过编码先调用查询接口,查出后面操作需要的一些字段值再进行后续的操作。所以管理 id 不是很适用于我们现在的测试,大程度是管理不起来的。

到后面有需要那就把配置项改为不是人写上去,而是程序自动生成和记录即可。

但是我对这个 id 值或是一些非目标测试项,但是需要传入的值自动生成和记录这一点,觉得很棒,能问下你这边指的思路也是通过调用接口去生成吗?但是抽取出来?是之前没有想到过的一点,可能更局限于某个接口需要,而忽略了通用性,是抽取出来类似通用组件去使用吗?

这个是老系统持续重构的常见问题,严格不是技术问题,而是取舍。最低成本当然是统一架构后再直接基于新架构写接口用例,但这样价值也最低,因为重构过程中用不上。如果重构也希望有自动化协助,那就要增加成本(新老架构各写一套用例)。

关于这一点,上面提到过的,现在的开发测试比有一点高,个人角度是非常想把 api 自动化这块做起来的,但是在需要保证正常功能测试迭代之外可以自由分配的时间中,无法实时跟进项目迭代开发的产出速度,所以能付出的成本是将已经翻新的部分从优先级高到底来实现接口自动化,在架构切换中我认为还是相对有效果的,因为在业务层也随之做了一些升级,那么新产出的一个代码,并且需要持续更新几个迭代甚至更久,还是有很多可预料的问题的。

至于接口调用实际使用协议的差异,其实本质上所有协议都是数据传输加接收。框架设计得好数据应该是抽象的模型(比如 model 层),具体协议可以直接基于模型写生成方法(如 json、xml、yaml 等),请求发送也是类似,统一命名为 send 方法,使用者只需要用这个方法即可。至于实际用的是 http 还是别的协议,由负责维护这个接口的同学写具体实现即可。我理解并不是不能做,而是这么做框架设计会变得复杂一些,所以需要像上面说的,衡量投入产出比。

关于最后一点,“其实本质上所有协议都是数据传输加接收” 这一点我时间认同的,有两点疑问,一个是抽象出来的 model 是包括了 url、header,请求内容和期望的吗?现在只是基于 testng 将请求内容和期望外加描述信息做了一个抽取,请求的 url 是单独的一个配置,能具体描述下 model 的全貌吗?另外,你这边指的 send 方法类似于 httpClient 中的 execute 这种感觉吗?

她们的诉求是造数据能不能只给出看到 code 值(页面上实际展示的编码),id 界面上看不到,也不知道是什么

如果 code 和你接口用到的 id 有一一对应关系,我理解也可以呀,你在底层做一个 codeToId 的转换就好,根据 code 到数据库查 id 。不过说实话,如果界面看不到就不知道怎么查的话,感觉做接口测试这类相对灰盒的测试,有点早?

能问下你这边指的思路也是通过调用接口去生成吗?但是抽取出来?

调用接口后,去数据库查出来。没有通用组件那么复杂。我们的业务是借贷业务,贯穿全程的是用户和账单信息,1:n 关系(一个用户可以多个订单),所以做了一个 model 统一存储一次流程中这些会强相互关联的信息。model 里面的值可以从手动写的配置文件取,配置文件没有那就调用对应造数据方法造出来 set 到 model 的实例里,这些写在一些全局 setUp 的方法里就行。

关于最后一点,“其实本质上所有协议都是数据传输加接收” 这一点我时间认同的,有两点疑问,一个是抽象出来的 model 是包括了 url、header,请求内容和期望的吗?现在只是基于 testng 将请求内容和期望外加描述信息做了一个抽取,请求的 url 是单独的一个配置,能具体描述下 model 的全貌吗?另外,你这边指的 send 方法类似于 httpClient 中的 execute 这种感觉吗?

url、header 本身就是 http 协议的一部分,应该属于请求执行方面的东西,model 不需要包含。model 只需要管理这个接口对应的数据模型即可。这只是抛砖引玉哈,我们公司内部项目基本上是比较统一的 http 协议,所以暂时用不到这个。我这里的思路有点类似于 rpc 调用,程序只需要知道方法,直接去调用即可,具体用什么协议由 rpc 框架来负责。比如 dubbo 支持多种不同的网络传输协议 (https://dubbo.apache.org/zh-cn/docs/user/references/protocol/http.html),切换协议只需要调整一些配置,不用调整代码编写方式。
send 方法类似于你说的 execute ,本质上就是一个通用接口,固定函数的格式。不同协议,提供不同的实现即可。

陈恒捷 回复

codeToId 觉得可以尝试,之前接口就是我写她们用的模式,所以更注重用例入参设计的简单性,虽然内部是做了转换的,但是这部分通用没有抽去出来。

后面这个我大致理解了,会找个例子去实践一下,感谢~

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册