往期文章

专栏文章 质量保障系统的落地实践 (一) 概览篇
专栏文章 质量保障系统的落地实践 (二) 项目管理设计 - 基础信息与缺陷信息设计
专栏文章 质量保障系统的落地实践 (二) 项目管理设计 - 代码信息设计
专栏文章 质量保障系统的落地实践 (三) CI 管理设计 - 基础设计
专栏文章 质量保障系统的落地实践 (三) CI 管理设计 - 集成设计

前言

书接上文, CI 管理设计 - 集成设计篇介绍了质量保障系统如何与自动化框架建立有效关联,本篇文章就在这个基础上做发散介绍。在实际测试过程中,往往有一定的造数需求—通过自动化脚本,创造出符合测试所需的数据。可见造数脚本也是需要使用自动化脚本编程的,但同时造数脚本又不同于自动化校验校验。一来造数脚本往往不需要校验逻辑,只需要执行创建数据即可;二来造数脚本往往需要具备根据外部传参来生成满足期望的数据,而不全是随机数据。将积累下来的造数脚本统一管理起来,就是接下来我们要谈谈的造数工厂。

造数脚本编写

由于造数脚本需要依据外部需求来改变生成的数据属性,那么该怎么处理?举一个具体的例子,比如场景上需要生成一个具备姓名、身份证、性别的用户,若是没有指定姓名、身份证、性别,则随机生成;若是外部指定姓名、身份证、性别,则使用外部的数据,那么伪代码的形式:

def DiyPerson(name=None, idcard=None, sex=None):
  if name is None:
    name = randomName()
  if idcard is None:
    idcard = randomIdCard()
  if sex is None:
    sex = randomSex()
  data = {
    "name": name,
    "sex": sex,
    "idcard": idcard
  }
  response = requests.post(url, json=data)

上述方式是比较常见的处理思路,所有变量均设置一个默认空值,若是没有传递,则随机生成数据。只是编写相对比较繁琐,特别是遇上表单项特别多的情况下,那么该怎么处理比较合适?大家注意到最后发起接口请求的时候,需要拼接data主体,里面包含了所有数据内容,是不是只要想办法把 data 的数据替换了就可以了:

def repalceRequestData(request_data, replace_data):
  data_ = {}
  for key, value in request_data.items():
    if key in replace_data:
      value = replace_data[key]
    data_[key] = value
  return data_

def DiyPerson(replace_data={}):
   name = randomName()
   idcard = randomIdCard()
   sex = randomSex()
   data = {
    "name": name,
    "sex": sex,
    "idcard": idcard
  }
  data_ = repalceRequestData(data, replace_data)
  response = requests.post(url, json=data_)

使用外部传递的 replace_data 属性,在接口调用前完成属性替换。那么可能有人要问,这么写的话,name、idcard、sex 属性的赋值动作重复了,不是会产生执行效率浪费吗?确实,这个方案会有一定执行效率的损耗,但是也有个明显的好处,就是改造成本很小。一般来说在设计自动化脚本的时候,为了脚本能够多次执行,很多数据往往采用随机生成的方案,如上述例子中的身份证、为避免身份证重复,所以代码中往往不会写死某一数据,所以自动化脚本的编写模板一般如下:

def DiyPerson():
   name = randomName()
   idcard = randomIdCard()
   sex = randomSex()
   data = {
    "name": name,
    "sex": sex,
    "idcard": idcard
  }
  response = requests.post(url, json=data)

大家可以对比一下传入 replace_data 的方案与自动化脚本编写的模板方案的差别,就可以很直观地看到使用 replace_data 的造数脚本方案只需要在自动化脚本的基础上完成少量改造即可,即:1、调用repalceRequestData方法;2、传入 replace_data。按照这样的方案编写造数脚本的效率将会大幅提升。

造数脚本触发

在 CI 管理的部分,我们提到了基于不同公司基建的处理方法,此处也是类似。若是公司有完整的执行平台,我们就可以借助这个平台完成造数脚本的触发;若是公司不具备条件,那么我们就通过命令行自行触发造数脚本。有兴趣的小伙伴可以翻看专栏文章 质量保障系统的落地实践 (三) CI 管理设计 - 集成设计

造数脚本反馈

谈完了造数脚本的触发,接下来很自然的就会想到触发后的结果如何反馈了。还是以上述的例子来讲解,我们通过脚本定制了一个名叫张三、性别为男的用户信息,那么这条数据是否生成,生成后用户的 ID 等信息如何告知调用者?这又是一个问题。
若是公司具备自动化执行平台,虽然可以解决调用的问题,但是同时会带来另一个问题,这类平台的执行结果反馈往往是通用模板,并不能完全满足造数需求的反馈:

若是公司不具备自动化执行平台,那么就更无从说起执行反馈的情况了。那么该怎么办?
解决方案就是我们自行补全这部分能力,我们编写一套脚本,待执行完成后,将反馈给质量保障平台。接下来还是以具体的例子来说明:
1、首先我们需要创建用户之后,自定义生成通知我们的消息体:

data = {
    "name": name,
    "sex": sex,
    "idcard": idcard
  }
  data_ = repalceRequestData(data, replace_data)
  response = requests.post(url, json=data_)
  try:
    response = response.json()
    userId = response["data"]["id"]
    msg = f"创建用户成功,传参:{data_},用户id:{userId}."
    success = true
  except json.JsonDecodeError:
    msg = f"创建用户失败,传参:{data_}."
    success = false
   except KeyError:
    msg = f"创建用户失败,传参:{data_},接口响应:{response}."
    success = false

  # 至此我们可以标记出此次执行是否成功,以及成功或失败的具体信息,将信息存储到文件中
  with open(result.json, "w") as f:
    item = {
      "success": success,
      "msg": msg
  }
    f.write(json.dumps(item))

2、其次已经将消息存储在了 result.json 文件中,那么接下来只需要解析这个文件,想办法把这个文件的 json 内容回调给质量保障平台即可。若是有执行平台,这类平台往往使用 jenkins 等集成方案,允许加入执行脚本,那么只要将解析 result.json 文件,调用质量保障平台的接口的脚本嵌入就能完成反馈回调;若是没有执行平台,那么就更方便处理了,举个例子,如果执行脚本的命令为:python3 userFlow.py,那么我们只需要在命令后拼接上执行回调函数的命令即可:python3 userFlow.py;python3 callback.py,由于命令行是顺序执行的,所以命令:python3 userFlow.py;python3 callback.py将保证完成造数脚本执行,反馈信息写入 result.json 文件后,再执行 callback.py 的脚本,这样我们就可以在这个 callback.py 文件中处理读取 json 文件,回调质量保障平台的逻辑:

# callback.py
with open(result.json,) as f:
   content = json.loads(f.read())
   response = requests.post(url, json=content)

3、最后质量保障平台处理回调数据:

msg = data["msg"]
success = data["success"]
if success:
  notify_msg = f"造数执行成功,执行信息:{msg}."
else:
  notify_msg = f"造数执行失败,执行信息:{msg}."

# 通知调用方
dingTalkNotify(webhook, notify_msg)

人效统计

既然完成了整个造数工厂的搭建,就要考虑数据沉淀的问题了,毕竟没有数据支撑,价值很难体现。既然打通了整个造数逻辑,那么我们只需要在每个造数脚本上增加预估节约的造数时间成本,如 5 分钟、7 分钟的配置。在触发造数脚本之前,先保存一条造数执行日志记录,将状态置为进行中。当造数脚本完成回调质量保障平台时,将这条记录置为完成,统计时将所有完成状态的执行日志进行汇总,统计节约的时长,就可以得出节约人效。

# 效能管理-造数脚本配置
class DataFactoryConfigInfo(BaseModel):
    department_id = models.IntegerField(verbose_name=u"归属组织节点")
    name = models.CharField(max_length=150, verbose_name=u"造数脚本名称")
    per_period = models.IntegerField(default=0, verbose_name=u"单次节约时间(分)")
    owner_name = models.CharField(max_length=11, verbose_name=u"负责人姓名")
    owner_phone = models.CharField(max_length=11, verbose_name=u"负责人手机号")

    class Meta:
        db_table = "Data_Factory_Config_Info"
        verbose_name = "效能管理-造数脚本配置"
        verbose_name_plural = verbose_name

# 效能管理-造数脚本执行日志
class DataFactoryLogInfo(BaseModel):
    department_id = models.IntegerField(verbose_name=u"归属组织节点")
    config_id = models.IntegerField(verbose_name=u"造数脚本配置id")
    name = models.CharField(max_length=150, verbose_name=u"造数脚本名称")
    period = models.IntegerField(default=0, verbose_name=u"节约时间(分)")
    owner_name = models.CharField(max_length=11, verbose_name=u"负责人姓名")
    owner_phone = models.CharField(max_length=11, verbose_name=u"负责人手机号")
    env = models.CharField(max_length=20, verbose_name=u"执行环境")
    is_done = models.BooleanField(default=False, verbose_name=u"是否执行完成")
    caller_name = models.CharField(null=True, blank=True, max_length=11, verbose_name=u"调用者姓名")
    caller_phone = models.CharField(null=True, blank=True, max_length=11, verbose_name=u"调用者手机号")
    response = models.TextField(null=True, blank=True, verbose_name=u"造数调用结果")

    class Meta:
        db_table = "Data_Factory_Log_Info"
        verbose_name = "造数脚本执行日志"
        verbose_name_plural = verbose_name

总结

本篇文章基于 CI 管理部分进行的拓展介绍。若是能完全理解这些设计思路,那么我们测试人员搭建质量保障平台的能力将会得到一定的提升,可以不那么依赖现成的基建能力,更不会受制于基建能力,希望能够给大家一些帮助,谢谢。


↙↙↙阅读原文可查看相关链接,并与作者交流