51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 724|回复: 0
打印 上一主题 下一主题

[转贴] 接口自动化测试分层设计与实践总结

[复制链接]
  • TA的每日心情
    擦汗
    13 小时前
  • 签到天数: 1048 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2022-7-29 09:25:59 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    本文以笔者当前使用的自动化测试项目为例,浅谈分层设计的思路,不涉及到具体的代码细节和某个框架的实现原理,重点关注在分层前后的使用对比,可能会以一些伪代码为例来说明举例。
      接口测试三要素:
      ·参数构造
      · 发起请求,获取响应
      · 校验结果
      一、原始状态
      当我们的用例没有进行分层设计的时候,只能算是一个“苗条式”的脚本。以一个后台创建商品活动的场景为例,大概流程是这样的(默认已经是登录状态下):
      创建商品-创建分类-创建优惠券-创建活动
      要进行接口测试的话,按照接口测试的三要素来进行,具体的效果如下:
    1. 1、参数构造
    2.       createCommodityParams = {
    3.           "input": {
    4.               "title": "活动商品",
    5.               "subtitle": "",
    6.               "brand": "",
    7.               "categoryLevel1Code": "12",
    8.               "categoryLevel2Code": "1312",
    9.               "categoryLevel3Code": "131211",
    10.               "detail": [
    11.                   {
    12.                       "uri": "ecommerce/1118d9.jpg",
    13.                       "type": 0
    14.                   }
    15.               ],
    16.               "installInfo": {
    17.                   "installType": 1,
    18.                   "installFee": null
    19.               },
    20.               "pictureList": [
    21.                   {
    22.                       "uri": "ecommerce/222.jpg",
    23.                       "main": true
    24.                   }
    25.               ],
    26.               "postageInfo": {
    27.                   "postageType": 2,
    28.                   "postageFee": 1,
    29.                   "postageId": null
    30.               },
    31.               "sellerDefinedCode": "",
    32.               "publish": 1,
    33.               "skuList": [
    34.                   {
    35.                       "skuCode": "",
    36.                       "externalSkuCode": "",
    37.                       "price": 1,
    38.                       "retailPrice": 6,
    39.                       "stock": 100,
    40.                       "weight": 0,
    41.                       "suggestPrice": 0,
    42.                       "skuAttrValueList": [
    43.                           {
    44.                               "attrCode": "COLOR",
    45.                               "attrName": "颜色",
    46.                               "attrValue": "绿色",
    47.                               "attrValueId": "1001"
    48.                           }
    49.                       ]
    50.                   }
    51.               ],
    52.               "jumpSwitch":false,
    53.               "recommendCommodityCodeList": [],
    54.               "recommendFittingCodeList": [],
    55.               "mallCode": "8h4xxx"
    56.           }
    57.       }
    58.       createCategoryParams = {......}
    59.       createCouponParams = {......}
    60.       createPublicityParams = {......}
    61.       publishCommodityParams = {......}
    62.       publishPublicityParams = {......}
    63.       createCommodityParams["input"]["title"] = "autoTest" + str(time.time())
    64.       createCommodityParams["input"]["mallCode"] = self.mallCode
    65.       createCommodityParams["input"]["skuList"][0]["price"] = random.randint(1,10)
    66.       createCategoryParams["input"]["categoryName"] = "autoTestCategory" + str(time.time())
    67.       createCouponParams。。。
    68.       createPublicityParams。。。
    69.       publishCommodityParams。。。
    70.       publishPublicityParams。。。
    71.       # 2、发起请求,获取响应
    72.        # 创建商品并获取商品code
    73.       createCommodityRes = api.getUrl("testApi.create.commodity").post.params(createCommodityParams)
    74.       commodityCode = createCommodityRes["commodityCode"]
    75.        # 创建分类并获取分类code
    76.       createCategoryRes = api.getUrl("testApi.create.category").post.params(createCategoryParams)
    77.       categoryCode = createCategoryRes["categoryCode"]
    78.        # 创建优惠券并获取优惠券code
    79.       createCouponRes = api.getUrl("testApi.create.coupon").post.params(createCouponParams)
    80.       couponCode = createCouponRes["couponCode"]
    81.        # 创建活动并关联商品,绑定优惠券,设置分类
    82.       createPublicityParams["input"]["commodityCode"] = commodityCode
    83.       createPublicityParams["input"]["categoryCode"] = categoryCode
    84.       createPublicityParams["input"]["couponCode"] = couponCode
    85.       createPublicityRes = api.getUrl("testApi.create.publicity").post.params(createPublicityParams)
    86.       # 结果校验(断言)
    87.       assert.equal(createPublicityRes["code"], 0)
    88.       assert.equal(createPublicityRes["publicityName"], createPublicityParams["publicityName"])
    89.       。。。
    复制代码
    按照上面的写法,对于单个脚本的调式来说或许可以,但是一旦用例的数量和复杂程度积累起来后,其维护成本将是巨大的,或者可以说不具备可维护性。
      弊端说明:
      ·可读性差,所有的处理都放在一起,代码量大,不简洁直观
      · 灵活性差,参数写死在脚本,适用用例范围小
      · 复用性差,如果其他用例需要同样或类似的步骤,需要重新写一份
      · 维护性差,如果接口有任何改动,那么所有涉及到此接口的脚本都需要一一修改
      例如:随着用例场景的增加,就可能会出现下面这种情况。

    按照原始的模式,我们就需要些3个脚本文件分别来描述着3个场景,并且创建商品_API、创建分类_API、创建优惠券_API在场景1,2,3中均出现了;上架商品_API在场景2,3中均出现。由此我们完全可以预见到,当几百上千的用例场景出现后,这种形式是没有维护性可言的。
      二、进化历程
      因此我们依照着痛点,以最开始的原始状态为例,对用例进行分层改造,来看看进化后的状态。
      1、API 定义层
      我们编程的时候会将一些重复的代码进行封装使用,那么这里依然可以借用这种思想,我们将 API 的定义单独抽离,单独定义。
      我们期望的效果是这样的:


    提前将API的定义放在一层,供用例场景引用,这样当接口有任何修改时,我们只需要修改API definition层即可。
      实例演示
      对应着上面的demo,我们就是需要做如下抽离:
    1. class APIDefinition:'''创建商品API定义 createCommodityParams: 创建商品接口入参 return:创建商品接口响应结果 ''' def createCommodityRequest(createCommodityParams): return api.getUrl("testApi.create.commodity").post.params(createCommodityParams)
    复制代码
    1.  '''
    2.        创建分类API定义
    3.        createCategoryParams: 创建分类接口入参
    4.        return:创建分类接口响应结果
    5.        '''
    6.        def createCategoryRequest(createCategoryParams)
    7.         return api.getUrl("testApi.create.category").post.params(createCategoryParams)
    8.        # 创建优惠券接口定义
    9.        def createCouponRequest(createCouponParams)
    10.         return api.getUrl("testApi.create.coupon").post.params(createCouponParams)
    11.        # 创建活动接口定义
    12.        def createPublicityRequest(createPublicityParams)
    13.         return api.getUrl("testApi.create.publicity").post.params(createPublicityParams)
    14.        # ...其余省略
    复制代码
    2、Service 层
      上面我们已经将接口的定义抽离出来,解决了 API 重复定义的问题,但是再继续分析会发现有一个问题依然没有解决,就是场景的复用性.
      再看刚才的图:


    3个场景中都有重复的步骤,类似创建商品、创建分类、创建优惠券这些,并且这些步骤都是一个个API的组合,一个步骤对应一个API,在各个步骤之间还会有数据的处理与传递,为了解决这些问题,将对场景再次做抽离,这里我称之为 service层。
      这一层之所以叫做?service(服务)层,是因为它的作用是用来提供测试用例所需要的各种“服务”,好比参数构建、接口请求、数据处理、测试步骤。
      用下图先来看分层的目标:

    我们希望将常用的测试场景步骤封装至service层中,供用例场景调用,增加复用性,也可以理解为测试用例的前置处理;
      但是这里还是有一点小问题,就是service层的东西太多太杂,有些场景步骤可能只适用于我当前的项目用例,在实际的工作中,各个系统间是相互依赖的,前台APP的测试很大可能就依赖后台创建作为前置条件
      好比我在APP端只要商品和分类,可能只想创建商品和分类,并不想创建优惠券,这个时候service层就没有适用的场景步骤供调用,那么我就需要根据自己的需要重新封装;可是对于很多单接口的前置数据处理又是一致的,比如:
    1.  ["input"]["title"] = "autoTest" + str(time.time())
    2.           createCommodityParams["input"]["mallCode"] = self.mallCode
    3.           createCommodityParams["input"]["skuList"][0]["price"] = random.randint(1,10)
    4.           createCategoryParams["input"]["categoryName"] = "autoTestCategory" + str(time.time())
    5.           createCouponParams。。。
    6.           createPublicityParams。。。
    7.           publishCommodityParams。。。
    8.           publishPublicityParams。。。
    复制代码
    重新封装的话还要再处理这一步,就有点麻烦且不符合我们的复用性设计了,因此我们对service层再细化为3层,分别为:
      apiObject:
      单接口的预处理层,这一层主要作用是单接口入参的构造,接口的请求与响应值返回
      每个接口请求不依赖与业务步骤,都是单接口的请求;
      此外一些简单固定的入参构建也直接放在这里处理,比如随机的商品名,title等,和具体业务流程无关,针对所有调用此接口的场景均适用。
      caseService:
      多接口的预处理层,这一层主要是??测试步骤(teststep)??或场景的有序集合。
      用例所需要的步骤,通过每一个请求进行组合,每一个步骤都对应着一个API请求,这些步骤会组成一个个场景,各个场景之间可以互相调用组成新的场景,以适应不同的测试用例需求。
      场景封装好以后可以供不同的测试用例调用,除了当前项目的用例,其他业务线需要的话也可从此??caseService??中选择调用,提高复用性的同时也避免了用例相互依赖的问题。
      util:
      这一层主要放置针对当前业务的接口需要处理的数据。
      在实际编写测试步骤时,可能部分接口的参数是通过其他接口获取后经过处理才可以使用,或是修改数据格式,或是修改字段名称,亦或是某些 value 的加解密处理等。
      细化分层后,各层的职责便更加清晰明确,具体如下图:










    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-18 22:37 , Processed in 0.067997 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表