接口自动化测试分层设计与实践总结_api自动化分层,2024年最新从零开始学软件测试编程

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新软件测试全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注软件测试)
img

正文

接口测试三要素

  • 参数构造
  • 发起请求,获取响应
  • 校验结果
一、原始状态

当我们的用例没有进行分层设计的时候,只能算是一个“苗条式”的脚本。以一个后台创建商品活动的场景为例,大概流程是这样的(默认已经是登录状态下):

创建商品-创建分类-创建优惠券-创建活动

要进行接口测试的话,按照接口测试的三要素来进行,具体的效果如下:

1、参数构造 createCommodityParams = { “input”: { “title”: “活动商品”, “subtitle”: “”, “brand”: “”, “categoryLevel1Code”: “12”, “categoryLevel2Code”: “1312”, “categoryLevel3Code”: “131211”, “detail”: [ { “uri”: “ecommerce/1118d9.jpg”, “type”: 0 } ], “installInfo”: { “installType”: 1, “installFee”: null }, “pictureList”: [ { “uri”: “ecommerce/222.jpg”, “main”: true } ], “postageInfo”: { “postageType”: 2, “postageFee”: 1, “postageId”: null }, “sellerDefinedCode”: “”, “publish”: 1, “skuList”: [ { “skuCode”: “”, “externalSkuCode”: “”, “price”: 1, “retailPrice”: 6, “stock”: 100, “weight”: 0, “suggestPrice”: 0, “skuAttrValueList”: [ { “attrCode”: “COLOR”, “attrName”: “颜色”, “attrValue”: “绿色”, “attrValueId”: “1001” } ] } ], “jumpSwitch”:false, “recommendCommodityCodeList”: [], “recommendFittingCodeList”: [], “mallCode”: “8h4xxx” } } createCategoryParams = {…} createCouponParams = {…} createPublicityParams = {…} publishCommodityParams = {…} publishPublicityParams = {…} createCommodityParams[“input”][“title”] = “autoTest” + str(time.time()) createCommodityParams[“input”][“mallCode”] = self.mallCode createCommodityParams[“input”][“skuList”][0][“price”] = random.randint(1,10) createCategoryParams[“input”][“categoryName”] = “autoTestCategory” + str(time.time()) createCouponParams。。。 createPublicityParams。。。 publishCommodityParams。。。 publishPublicityParams。。。 # 2、发起请求,获取响应 # 创建商品并获取商品code createCommodityRes = api.getUrl(“testApi.create.commodity”).post.params(createCommodityParams) commodityCode = createCommodityRes[“commodityCode”] # 创建分类并获取分类code createCategoryRes = api.getUrl(“testApi.create.category”).post.params(createCategoryParams) categoryCode = createCategoryRes[“categoryCode”] # 创建优惠券并获取优惠券code createCouponRes = api.getUrl(“testApi.create.coupon”).post.params(createCouponParams) couponCode = createCouponRes[“couponCode”] # 创建活动并关联商品,绑定优惠券,设置分类 createPublicityParams[“input”][“commodityCode”] = commodityCode createPublicityParams[“input”][“categoryCode”] = categoryCode createPublicityParams[“input”][“couponCode”] = couponCode createPublicityRes = api.getUrl(“testApi.create.publicity”).post.params(createPublicityParams) # 结果校验(断言) assert.equal(createPublicityRes[“code”], 0) assert.equal(createPublicityRes[“publicityName”], createPublicityParams[“publicityName”]) 。。。

复制代码

按照上面的写法,对于单个脚本的调式来说或许可以,但是一旦用例的数量和复杂程度积累起来后,其维护成本将是巨大的,或者可以说不具备可维护性。

弊端说明:

可读性差,所有的处理都放在一起,代码量大,不简洁直观

灵活性差,参数写死在脚本,适用用例范围小

复用性差,如果其他用例需要同样或类似的步骤,需要重新写一份

维护性差,如果接口有任何改动,那么所有涉及到此接口的脚本都需要一一修改

例如:随着用例场景的增加,就可能会出现下面这种情况

按照原始的模式,我们就需要些3个脚本文件分别来描述着3个场景,并且创建商品_API、创建分类_API、创建优惠券_API在场景1,2,3中均出现了;上架商品_API在场景2,3中均出现。由此我们完全可以预见到,当几百上千的用例场景出现后,这种形式是没有维护性可言的。

二、进化历程
因此我们依照着痛点,以最开始的原始状态为例,对用例进行分层改造,来看看进化后的状态。

1、API 定义层

我们编程的时候会将一些重复的代码进行封装使用,那么这里依然可以借用这种思想,我们将 API 的定义单独抽离,单独定义。

我们期望的效果是这样的:

提前将API的定义放在一层,供用例场景引用,这样当接口有任何修改时,我们只需要修改API definition层即可。

实例演示

对应着上面的demo,我们就是需要做如下抽离:

class APIDefinition:

‘’’ 创建商品API定义 createCommodityParams: 创建商品接口入参 return:创建商品接口响应结果 ‘’’ def createCommodityRequest(createCommodityParams): return api.getUrl(“testApi.create.commodity”).post.params(createCommodityParams) ‘’’ 创建分类API定义 createCategoryParams: 创建分类接口入参 return:创建分类接口响应结果 ‘’’ def createCategoryRequest(createCategoryParams) return api.getUrl(“testApi.create.category”).post.params(createCategoryParams) # 创建优惠券接口定义 def createCouponRequest(createCouponParams) return api.getUrl(“testApi.create.coupon”).post.params(createCouponParams) # 创建活动接口定义 def createPublicityRequest(createPublicityParams) return api.getUrl(“testApi.create.publicity”).post.params(createPublicityParams) # …其余省略
复制代码

2、Service 层

上面我们已经将接口的定义抽离出来,解决了 API 重复定义的问题,但是再继续分析会发现有一个问题依然没有解决,就是场景的复用性.

再看刚才的图:

3个场景中都有重复的步骤,类似创建商品创建分类创建优惠券这些,并且这些步骤都是一个个API的组合,一个步骤对应一个API,在各个步骤之间还会有数据的处理与传递,为了解决这些问题,将对场景再次做抽离,这里我称之为 service 层。

这一层之所以叫做service(服务)层,是因为它的作用是用来提供测试用例所需要的各种“服务”,好比参数构建、接口请求、数据处理、测试步骤。

用下图先来看分层的目标:

我们希望将常用的测试场景步骤封装至service层中,供用例场景调用,增加复用性,也可以理解为测试用例的前置处理;

但是这里还是有一点小问题,就是service层的东西太多太杂,有些场景步骤可能只适用于我当前的项目用例,在实际的工作中,各个系统间是相互依赖的,前台APP的测试很大可能就依赖后台创建作为前置条件

好比我在APP端只要商品和分类,可能只想创建商品和分类,并不想创建优惠券,这个时候service层就没有适用的场景步骤供调用,那么我就需要根据自己的需要重新封装;可是对于很多单接口的前置数据处理又是一致的,比如:

createCommodityParams[“input”][“title”] = “autoTest” + str(time.time()) createCommodityParams[“input”][“mallCode”] = self.mallCode createCommodityParams[“input”][“skuList”][0][“price”] = random.randint(1,10) createCategoryParams[“input”][“categoryName”] = “autoTestCategory” + str(time.time()) createCouponParams。。。 createPublicityParams。。。 publishCommodityParams。。。 publishPublicityParams。。。
复制代码

重新封装的话还要再处理这一步,就有点麻烦且不符合我们的复用性设计了,因此我们对service层再细化为3层,分别为:

apiObject

单接口的预处理层,这一层主要作用是单接口入参的构造,接口的请求与响应值返回

  • 每个接口请求不依赖与业务步骤,都是单接口的请求;
  • 此外一些简单固定的入参构建也直接放在这里处理,比如随机的商品名,title等,和具体业务流程无关,针对所有调用此接口的场景均适用

caseService

多接口的预处理层,这一层主要是测试步骤(teststep)或场景的有序集合。

用例所需要的步骤,通过每一个请求进行组合,每一个步骤都对应着一个API请求,这些步骤会组成一个个场景,各个场景之间可以互相调用组成新的场景,以适应不同的测试用例需求。

场景封装好以后可以供不同的测试用例调用,除了当前项目的用例,其他业务线需要的话也可从此caseService中选择调用,提高复用性的同时也避免了用例相互依赖的问题。

util:

这一层主要放置针对当前业务的接口需要处理的数据

  • 在实际编写测试步骤时,可能部分接口的参数是通过其他接口获取后经过处理才可以使用,或是修改数据格式,或是修改字段名称,亦或是某些 value 的加解密处理等。

细化分层后,各层的职责便更加清晰明确,具体如下图:

实例演示

apiObject:

class ApiObject: def createCommodity(createCommodityParams): inputParams = ApiParamsBuild().createCommodityParamsBuild(createCommodityParams) response = APIDefinition().createCommodityRequest(inputParams) return response def createCategory(createCategoryParams): … def createCoupon(createCouponParams): … … class ApiParamsBuild: def createCommodityParamsBuild(createCommodityParams): createCommodityParams[“input”][“title”] = “autoTest” + str(time.time()) createCommodityParams[“input”][“mallCode”] = self.mallCode createCommodityParams[“input”][“skuList”][0][“price”] = random.randint(1,10) return createCommodityParams def createCategoryParamsBuild(createCategoryParams): … def createCouponParamsBuild(createCouponParams): … …
复制代码

到此,我们来看看原始的用例经过目前封装后的模样:

1、参数构造 createCommodityParams = { “input”: { “title”: “活动商品”, “subtitle”: “”, “brand”: “”, “categoryLevel1Code”: “12”, “categoryLevel2Code”: “1312”, “categoryLevel3Code”: “131211”, “detail”: [ { “uri”: “ecommerce/1118d9.jpg”, “type”: 0 } ], “installInfo”: { “installType”: 1, “installFee”: null }, “pictureList”: [ { “uri”: “ecommerce/222.jpg”, “main”: true } ], “postageInfo”: { “postageType”: 2, “postageFee”: 1, “postageId”: null }, “sellerDefinedCode”: “”, “publish”: 1, “skuList”: [ { “skuCode”: “”, “externalSkuCode”: “”, “price”: 1, “retailPrice”: 6, “stock”: 100, “weight”: 0, “suggestPrice”: 0, “skuAttrValueList”: [ { “attrCode”: “COLOR”, “attrName”: “颜色”, “attrValue”: “绿色”, “attrValueId”: “1001” } ] } ], “jumpSwitch”:false, “recommendCommodityCodeList”: [], “recommendFittingCodeList”: [], “mallCode”: “8h4xxx” } } createCategoryParams = {…} createCouponParams = {…} createPublicityParams = {…} publishCommodityParams = {…} publishPublicityParams = {…} # 2、发起请求,获取响应 # 创建商品并获取商品code createCommodityRes = ApiObject().createCommodity(createCommodityParams) commodityCode = createCommodityRes[“commodityCode”] # 创建分类并获取分类code createCategoryRes = ApiObject().createCategory(createCategoryParams) categoryCode = createCategoryRes[“categoryCode”] # 创建优惠券并获取优惠券code createCouponRes = ApiObject().createCoupon(createCouponParams) couponCode = createCouponRes[“couponCode”] # 创建活动并关联商品,绑定优惠券,设置分类 createPublicityParams[“input”][“commodityCode”] = commodityCode createPublicityParams[“input”][“categoryCode”] = categoryCode createPublicityParams[“input”][“couponCode”] = couponCode createPublicityRes = ApiObject().createPublicity(createPublicityParams) # 结果校验(断言) assert.equal(createPublicityRes[“code”], 0) assert.equal(createPublicityRes[“publicityName”], createPublicityParams[“publicityName”]) 。。。

复制代码

可以看到,现在接口请求的url、method、通用入参处理等已经不会在用例中体现了,接下来继续封装caseService层。

caseService:

我们将多接口的场景步骤进行封装

class CaseService: def createPublicityByCategory(params): # 创建商品并获取商品code createCommodityRes = ApiObject().createCommodity(createCommodityParams) commodityCode = createCommodityRes[“commodityCode”] # 创建分类并获取分类code createCategoryRes = ApiObject().createCategory(createCategoryParams) categoryCode = createCategoryRes[“categoryCode”] # 创建优惠券并获取优惠券code createCouponRes = ApiObject().createCoupon(createCouponParams) couponCode = createCouponRes[“couponCode”] # 创建活动并关联商品,绑定优惠券,设置分类 createPublicityParams[“input”][“commodityCode”] = commodityCode createPublicityParams[“input”][“categoryCode”] = categoryCode createPublicityParams[“input”][“couponCode”] = couponCode createPublicityRes = ApiObject().createPublicity(createPublicityParams) return createPublicityRes …
复制代码

这时体现在用例中的表现就如下层testcase层所示.

3、testcase 层

我们想要的是一个清晰明了,“一劳永逸”的自动化测试用例,就像我们的手工测试用例一样,我们的前置条件可以复用,我们入参可以任意修改,但测试步骤都是固定不变的(前提可能是产品没有偷偷改需求~)。

这一层其实是对应的testsuite(测试用例集),是测试用例的无序集合。其中各个用例之间应该是相互独立,互不干扰,不存在依赖关系,每个用例都可以单独运行。

最终我们期望自动化用例的维护过程中达到的效果如下:

testcase 层:

1、参数构造 createCommodityParams = { “input”: { “title”: “活动商品”, “subtitle”: “”, “brand”: “”, “categoryLevel1Code”: “12”, “categoryLevel2Code”: “1312”, “categoryLevel3Code”: “131211”, “detail”: [ { “uri”: “ecommerce/1118d9.jpg”, “type”: 0 } ], “installInfo”: { “installType”: 1, “installFee”: null }, “pictureList”: [ { “uri”: “ecommerce/222.jpg”, “main”: true } ], “postageInfo”: { “postageType”: 2, “postageFee”: 1, “postageId”: null }, “sellerDefinedCode”: “”, “publish”: 1, “skuList”: [ { “skuCode”: “”, “externalSkuCode”: “”, “price”: 1, “retailPrice”: 6, “stock”: 100, “weight”: 0, “suggestPrice”: 0, “skuAttrValueList”: [ { “attrCode”: “COLOR”, “attrName”: “颜色”, “attrValue”: “绿色”, “attrValueId”: “1001” } ] } ], “jumpSwitch”:false, “recommendCommodityCodeList”: [], “recommendFittingCodeList”: [], “mallCode”: “8h4xxx” } } createCategoryParams = {…} createCouponParams = {…} createPublicityParams = {…} publishCommodityParams = {…} publishPublicityParams = {…} # 2、发起请求,获取响应 createPublicityRes = CaseService().createPublicityByCategory(createCommodityParams,createCategoryParams,createCouponParams…) # 结果校验(断言) assert.equal(createPublicityRes[“code”], 0) assert.equal(createPublicityRes[“publicityName”], createPublicityParams[“publicityName”]) 。。。

复制代码

可以看到,这时涉及到用例场景步骤的代码已经非常少了,并且完全独立,与框架、其他用例等均无耦合。

到这里我们再看用例,会发现一点,测试数据依然冗长,那么下面就开始对测试数据进行参数化数据驱动的处理。

4、testdata

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注软件测试)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
数据驱动`的处理。

4、testdata

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注软件测试)
[外链图片转存中…(img-aCbpPnoq-1713557688577)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值