测试平台系列(68) 解决数据驱动带来的麻烦

简介: 解决数据驱动带来的麻烦

大家好~我是米洛


我在从0到1打造一个开源平台, 也在编写一套完整的接口测试平台系列教程,希望大家能够多多支持。

回顾


上节内容我们编写好了数据驱动相关的接口,但却有很多困扰随之而来。接着就来说说我们提出这些困扰,并解决之。

问题汇总


由于数据驱动的改造,可能会引发以下几个问题:

  • 原先的执行用例的方式需要变化
    数据驱动以后,我们批量执行case的时候需要带上环境字段了,否则数据驱动没法使用。
  • 原先能够直接调试case,也就是单条case在线执行
    现在由于环境的关系+多数据的情况,我们只能把这块进行调整,让单个case的调试变为单条测试数据的调试。

1.jpg

可以看到现在执行case的时候会提示没有env参数,因为我们支持了数据驱动,必须带上执行环境


  • 执行测试用例逻辑的变化
    之前我们执行测试用例的时候,举个例子: 登录接口,我们是在前置条件里面写好了具体的登录用户名密码。但在数据驱动的情况下,又不一样了。
    我们得把用例里面变化的数据全部抽离出来,其实这么做的目的是为了让数据更灵活,也为了以后能支持数据工厂
    所以这就会导致我们执行case的整套逻辑会发生剧烈变化,让人头大!!!
  • 断言逻辑的改造
    细心的朋友可能会发现,断言部分之前没有很好地支持变量,但现在不一样,我们要根据不同的参数完成不同的断言操作,所以我们也得支持。

迎接挑战


虽然这些挑战比较麻烦,但好在大多是体力活儿。因为以前的逻辑和现在逻辑的区别引发,只要耐心,就能逐一击破。而且之前埋下的坑比如断言变量没替换等等都可以随之解决。

改造报告


  • PityTestResult新增字段
    之前我们编写报告的时候,都是一条case一条数据,那现在有了数据驱动,我们的报告就需要做出一定的改变
    2.jpg


数据驱动的2个核心字段,数据+标识


现在我们需要把数据的标识和数据的内容都写入报告,这样的话在查看报告的时候就能清楚地看到每条数据的执行结果,可以对号入座

  • PityTestResult初始化方法新增字段

3.jpg

image

  • 同理,插入测试结果的时候也要调整

4.jpg

image

  • 查询报告的时候也要相继调整

5.jpg

按照用例id+用例开始执行时间的顺序排列

改造用例执行器


我们之前的执行器有许多不完善的地方,比如不支持断言里面的数据变化,再者只替换了一次数据,而现在我们要做的是什么呢?

每当有新的变量生成,我们都要去寻找变量去替换,这样做虽然显得工作量很重复,但是完整地保证了生成的变量不会漏掉。

  • 编写replace_args方法

6.jpg

要替换的地方有4个,用例,前置条件,后置条件,断言

因为没法度量变量什么时候生成,所以要做的就是每当有新的变量产生就替换变量

  • replace方法

7.jpg

image

可以看到他们都调用了核心方法: replace_cls,也就是替换一个对象,通过属性+值的形式。

我们来看一下replace_cls方法

8.jpg

image

剖析一下: 遍历参数字典,接着去指定的对象字段里面查询是否有这个变量,什么意思呢?假设有这样一个对象:


class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

假设此时学生的name和age分别是${student_name}18

我们这边有个参数字典为:


params = {"student_name": "miro"}

很显然,我们要做的效果就是:

  1. 遍历params,此时key=student_name, value=miro
  2. 根据指定的字段: nameage(注意是个列表)
  3. 先找name,发现name的值是${student_name}(我们pity内部约定的变量形式),和student_name吻合,所以我们要把${student_name} 替换为 miro
  4. 调用setattr方法,把student_name替换成它应该变成的值: miro
  5. 由于age字段的值是18,并不是一个变量,所以循环结束

就是上面这个思路,不知道大家有没有看明白。

  • replace_params

这块是变量替换的精髓所在,也是最难弄懂的地方。

9.jpg

这里的request_headers是特殊情况,做的特殊处理

这个方法具体的处理逻辑就是从JSON字符串中找到对应的值,并能支持字段取值。

比如a = '{"human": {"age": 1}}'这个json字符串,如果我们要取到age,我们需要这么写${res.human.age}.

这个方法做到的事情就是,从res获取human,接着获取age,最终返回一个字段,用于替换:

new_data = {"human.age": 1}

完成上述操作,接着就能够从字符串中拿到human.age的值(1)了,从而能够将所有用例产生的变量分配到其他case或者其他数据。

这块之所以有一些改动,是因为我们把response里面的所有数据都改成了字符串,以前比如response可能是字典

改造数据构造器


10.jpg

数据构造器,势必会产生变量,产生变量则要进行一次替换操作(哪怕没产生变量)

run方法改造



async def run(self, env: int, case_id: int, params_pool: dict = None, request_param: dict = None, path="主case"):
        """
        开始执行测试用例
        """
        response_info = dict()
        # 初始化case全局变量, 只存在于case生命周期 注意 它与全局变量不是一套逻辑
        case_params = params_pool
        if case_params is None:
            case_params = dict()
        req_params = request_param
        if req_params is None:
            req_params = dict()
        try:
            case_info, err = await TestCaseDao.async_query_test_case(case_id)
            if err:
                return response_info, err
            response_info["case_name"] = case_info.name
            method = case_info.request_method.upper()
            response_info["request_method"] = method
            # Step1: 替换全局变量
            await self.parse_gconfig(case_info, *Executor.fields)
            self.append("解析全局变量", True)
            # Step2: 获取构造数据
            constructors = await self.get_constructor(case_id)
            # Step3: 获取断言
            asserts, err = await TestCaseAssertsDao.async_list_test_case_asserts(case_id)
            if err:
                return response_info, err
            # Step4: 替换参数
            self.replace_args(req_params, case_info, constructors, asserts)
            # Step5: 执行构造方法
            await self.execute_constructors(env, path, case_info, case_params, req_params, constructors, asserts)
            response_info["url"] = case_info.url
            # Step6: 获取后置操作
            # TODO
            # Step7: 批量改写主方法参数
            await self.parse_params(case_info, case_params)
            if case_info.request_headers != "":
                headers = json.loads(case_info.request_headers)
            else:
                headers = dict()
            if "Content-Type" not in headers:
                headers['Content-Type'] = "application/json; charset=UTF-8"
            if case_info.body != '':
                body = case_info.body
            else:
                body = None
            # Step5: 替换请求参数
            body = self.replace_body(request_param, body)
            # Step6: 完成http请求
            if "form" not in headers['Content-Type']:
                request_obj = AsyncRequest(case_info.url, headers=headers,
                                           data=body.encode() if body is not None else body)
            else:
                if body is not None:
                    body = json.loads(body)
                request_obj = AsyncRequest(case_info.url, headers=headers, data=body)
            res = await request_obj.invoke(method)
            self.append(f"http请求过程\n\nRequest Method: {case_info.request_method}\n\n"
                        f"Request Headers:\n{headers}\n\nUrl: {case_info.url}"
                        f"\n\nBody:\n{body}\n\nResponse:\n{res.get('response', '未获取到返回值')}")
            response_info.update(res)
            # 执行完成进行断言
            asserts, ans = self.my_assert(asserts, response_info)
            response_info["asserts"] = asserts
            # 日志输出, 如果不是开头用例则不记录
            if self._main:
                response_info["logs"] = self.logger.join()
            response_info["status"] = ans
            return response_info, None
        except Exception as e:
            Executor.log.error(f"执行用例失败: {str(e)}")
            self.append(f"执行用例失败: {str(e)}")
            if self._main:
                response_info["logs"] = self.logger.join()
            return response_info, f"执行用例失败: {str(e)}"

方法比较长,我就直接贴代码了。改造点主要有以下几处:

  1. 新增env参数
  2. replace_args替换掉replace_params
  3. 打印完整的http请求
  4. 在执行异常的时候,返回尽可能多的信息

改造执行方法


11.jpg

image

在执行之前呢,先获取这个case在对应环境的测试数据,然后将数据解析为json.

接着就是用异步的方式,挨个儿执行。

其他的比如批量执行,也和这个细节差不多,因为篇幅的问题就不讲了。



相关文章
|
29天前
Mybatis+mysql动态分页查询数据案例——测试类HouseDaoMybatisImplTest)
Mybatis+mysql动态分页查询数据案例——测试类HouseDaoMybatisImplTest)
20 1
|
29天前
|
Java 关系型数据库 数据库连接
Mybatis+MySQL动态分页查询数据经典案例(含代码以及测试)
Mybatis+MySQL动态分页查询数据经典案例(含代码以及测试)
24 1
|
2月前
|
计算机视觉
Google Earth Engine(GEE)——使用MODIS数据单点测试SG滤波和harmonics method 滤波的差异分析
Google Earth Engine(GEE)——使用MODIS数据单点测试SG滤波和harmonics method 滤波的差异分析
46 0
|
16天前
|
人工智能 分布式计算 Kubernetes
人工智能,应该如何测试?(三)数据构造与性能测试篇
本文探讨了人工智能场景中的性能测试,区别于传统互联网测试,其复杂性更高。主要关注点包括两类AI产品——业务类和平台类,后者涉及AI全生命周期,测试难度更大。测试重点是模型训练的性能,特别是数据模拟。需要构造大量结构化数据,如不同规模、分布、分片和特征规模的数据,以评估算法效率。此外,还涉及模拟设备规模(如视频流)和节点规模(边缘计算),以测试在大规模负载下的系统性能。文中提到了使用工具如Spark、ffmpeg、流媒体服务器和Kubernetes(K8S)的扩展项目,如Kubemark,来模拟大规模环境。最后,文章介绍了使用Golang进行异步IO操作以构建海量小文件,优化IO性能。
32 0
|
21天前
|
XML 存储 监控
深入理解自动化测试中的数据驱动策略
【4月更文挑战第2天】 随着软件开发周期的不断缩短和测试需求的日益增加,自动化测试已成为确保软件质量的关键手段。本文将深入探讨数据驱动测试(DDT)策略在自动化测试中的应用及其优势。数据驱动测试是一种将测试逻辑与测试数据分离的方法,通过外部数据源动态提供输入和预期结果,从而增强测试案例的灵活性和可扩展性。我们将分析数据驱动测试的核心原理、实施步骤以及如何利用它来提高测试覆盖率和效率。
|
25天前
|
存储 XML 测试技术
深入理解自动化测试中的数据驱动策略
在现代软件开发周期中,自动化测试已经成为提升效率、确保质量的关键手段。本文重点探讨了数据驱动测试(DDT)策略的核心原理及其在实际测试中的应用优势。通过将测试逻辑与测试数据分离,DDT能够增强测试案例的可重用性,简化测试维护,并提高测试覆盖率。文章详细分析了如何设计数据驱动测试框架,以及如何利用该框架进行有效的测试数据管理。同时,文中还讨论了实施DDT时可能遇到的挑战和限制,并提出了一系列解决方案。
|
29天前
|
XML 敏捷开发 数据管理
深入理解自动化测试中的数据驱动策略
在现代软件开发周期中,自动化测试是确保产品质量的关键步骤。数据驱动测试(DDT)是一种高效的自动化测试策略,它通过外部数据源来增强测试用例的灵活性和可维护性。本文将探讨数据驱动测试的核心原理、实施方法以及其在复杂测试场景中的应用优势。我们将分析真实案例,以展示如何利用数据驱动策略来提高测试覆盖率并优化回归测试流程。
|
1月前
|
存储 敏捷开发 测试技术
深入理解自动化测试中的数据驱动策略
【2月更文挑战第30天】在追求软件开发周期缩短与质量提升的当下,自动化测试已成为不可或缺的一环。本文将重点探讨数据驱动测试(DDT)策略,一种通过外部数据源来增强测试用例的方法。我们将分析数据驱动策略的优势、实施步骤以及面临的挑战,并通过具体案例展示如何在现有的自动化测试框架中集成数据驱动方法,旨在帮助读者构建更灵活、可维护且高效的自动化测试系统。
|
1月前
|
缓存 运维 Serverless
应用研发平台EMAS产品常见问题之测试检查更新没有反应如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
|
1月前
|
存储 XML 测试技术
深入理解自动化测试中的数据驱动策略
【2月更文挑战第29天】 在软件测试领域,自动化测试已成为提升测试效率和确保软件质量的关键手段。本文将重点探讨数据驱动测试(DDT)策略在自动化测试中的应用及其优势。不同于传统的摘要方式,本文将直接进入对数据驱动测试概念的剖析,以及它如何帮助测试工程师通过外部数据集来增强测试用例的可维护性和灵活性。我们将分析真实案例,展示如何实施DDT,并讨论其对测试覆盖率和可靠性的影响。