测试数据准备方法以及未来的发展方向

发表于:2019-05-16来源:TesterHome作者:刘春明点击数: 标签:
测试数据的准备,是软件测试工作中非常重要的环节,无论是手工测试还是自动化测试都避不开测试数据准备工作。今天我们就来聊一聊测试工作中常用的测试数据准备的方法,深入了

测试数据的准备,是软件测试工作中非常重要的环节,无论是手工测试还是自动化测试都避不开测试数据准备工作。今天我们就来聊一聊测试工作中常用的测试数据准备的方法,深入了解各自的优缺点和使用场景,以及测试数据准备工作未来的发展方向。

01 常见的测试数据准备方法

我总结了一下我曾经过用过的生成测试数据的方法,主要有以下几类:

  • 基于 GUI 的测试数据生成方法

  • 基于 API 的测试数据生成方法

  • 基于 DB 的测试数据生成方法

  • 基于 MQ 的测试数据生成方法

  • 基于第三方库方式的测试数据生成方法

  • 综合运用上述方法生成测试数据

接下来,我们一起详细分析一下各种方法的有权点以及适用场景。

基于 GUI 准备测试数据

基于 GUI 界面进行测试数据准备,是最原始的创建测试数据的方法,这种方法其实是采用 E2E 的方法来执行业务场景,然后得到测试数据。

比如,测试用户登录功能,那么需要准备的测试数据就是用户账号,为此我们可以通过 APP 或者 WEB 端的 GUI 页面注册新用户,然后用这个用户完成用户登录功能的测试。

这种方法的优点是简单直接,创建的数据来自于真实的业务流程,最大程度保证了数据的正确性和完整性。在很多手工测试的场景中,这种方法被普遍采用。

但是,这种方法的缺点也非常明显,主要体现在以下几个方面:

  • 创建测试数据的效率低,不适合批量生成测试数据。因为通过 GUI 操作每次只能创造一条数据,而且通过手工操作 GUI 的过程也是比较耗时。

  • 基于 GUI 的测试数据生成方法不适合为自动化测试提供数据。由于自动化测试往往是通过代码来准备测试数据,而 GUI 方法生成测试数据的方法不太适合封装成代码被自动化测试用例调用。因为封装 GUI 方式生成测试数据的方法,本质上是在开发 GUI 自动化测试用例,而我们知道无论是开发工作量还是执行效率,亦或是稳定性方面,这种方法都是不是最佳的选择。

  • 会引入不必要的测试依赖。比如测试用户登录功能,如果依赖 GUI 先注册一个用户,那么就意味着注册功能必须是没问题的,引入了依赖。这种情况,从数据库中找到一个已注册的账号来测试登录功能才是最佳选择。

在前后台配合的手工测试中,比如内容管理系统 CMS 和手机 APP 的测试,如果要手工测试手机 APP 的文章列表功能,那么就可以采用这种方法。除此之外,基于 GUI 操作生成测试数据的场景并不多。

基于 GUI 生成测试数据的方法,有一个非常重要的价值是帮助我们在创建测试数据的过程中,找到创建数据的过程中都调用了哪些 API 以及修改了哪些 DB 的表。只有了解了这两个方面,我们后续通过 API 或者修改 DB 方式创建测试数据时,才能保证数据的完整性。

基于 API 准备测试数据

通过调用 API 生成测试数据,是目前测试数据生成的主要方法。由于后台接口一般比较稳定,大大提供了测试数据构造的准确性和成功率。调用接口相比 GUI 操作也能够比较快速的创建测试数据,效率高。另外,由于我们直接给 API 传递参数,通过参数的组合可以构造成某些 GUI 方法不能构造出来的测试数据。

那么,我们如何获取到这些 API 呢?通常推荐按照下面的顺序,来查找 API 相关的信息。

  1. API 接口文档。通常成熟的开发团队,都会编写 API 的接口文档,接口文档中会详细描述接口的 URI 和调用参数,这是最直接有效的办法。

  2. 通过抓包。抓包在测试中是非常常用的辅助手段,我们可以在操作 APP 或者 WEB 页面的时候,对操作进行抓包,通过对抓取到的请求包,分析接口的各种参数。这也是相对高效的办法。

  3. 查看日志文件。对于已经上线的接口,我们可以通过服务的日志,来查看接口调用过程中的 URI 和参数等内容。

  4. 阅读源码。如果前面三种方法都不能用,那么可以在 Gitlab 上查看开发人员的项目代码,通过阅读代码的方法,找到接口请求的各种参数。

通过 API 构造测试数据的方法也不是完美的,主要有几个方面:

  1. 不是所有的数据创建都有对应的 API。

  2. 有时候需要顺序调用多个 API。有时候测试数据之间是有关联关系的,为了保证测试数据的完整性和一致性,需要依次调用多个 API,无形中增加了测试数据准备的复杂性。

调用 API 创建测试数据,天生适合与自动化测试相结合,在实际的测试实践中,我们往往会把 API 封装成测试数据准备函数供自动化测试用例使用。当 API 内部逻辑有修改时,我们依旧可以通过封装函数来准备测试数据,对测试用例来说,是完全透明的。

这里所说的 API 指的是基于 HTTP 协议的 RESTful API。但是可以扩展到其他协议的各种调用接口,比如 MQTT 协议、RPC 协议等。

基于 DB 准备测试数据

通过往数据库中直接插入数据,也是非常常用的构造测试数据的方法。具体做法是,将创建测试数据的 SQL 语句封装成一个个测试数据生成函数,当我们创建数据时,直接调用这些封装好的函数即可。这种方法有一个非常大的优点是生成测试数据的效率非常高,可以短时间内往数据库中插入大量的测试数据。

以用户登录功能测试为例,当我们调用 API 进行用户注册时,这个 API 会将用户的详细的信息插入到 user 表和 role 表两个数据库表中。如果我们采用数据库方式创造数据时,给 user 表和 role 表分别插入对应的数据就完成了用户的注册。我们还可以直接使用 DB 中已有的数据作为我们的测试数据,从而省去了很多操作。

这里的前提是,你必须知道进行新用户注册时,到底涉及到了哪些数据库的表。最直接的办法就是跟开发同学索要 SQL 语句,或者查看源代码。

这种构造测试数据的方法也不是完美的,主要体现在以下几个方面:

  1. 有的测试数据准备涉及到的数据表太多。导致封装和维护测试准备函数的成本比较高。

  2. 容易出现数据不完整和不一致。比如服务 A 某一个业务,实际会在服务 A 的数据库表 A 和数据库表 B 中分别插入数据,并且同时会给 kafka 的某个 topic 发送数据供服务 B 消费处理后持久化到服务 B 的数据库表 C 中。如果我们漏掉了某个数据库表的插入操作,可能会导致数据的不完整和不一致。

基于 DB 准备测试数据的方法,通常作为 API 方法的补充。

基于 MQ 准备测试数据

在微服务架构中,通常会存在通过消息中间件将多个服务进行解耦,为了减少测试工作的依赖,通常会往 kafka 中构造测试数据。

比如,两个服务是通过 KAFKA 进行消息传递的,两个服务分别作为 kafka 的生成者和消费者。当我们测试作为消费者的服务时,就可以编写 kafka 的 producer 代码,往 kafka 中生产测试所需要的测试数据。具体的做法与通过 DB 构造测试数据的方式类似,将 kafka 的 producer 代码封装成测试数据生成函数,当我们创建数据时,直接调用这些封装好的函数即可。

这种做法和操作 DB 并没有本质不同,其优点和缺点也是类似的。

基于第三方库准备测试数据

我们的测试实践中,经常会需要生成很多随机的数据,对于这类需求,直接使用代码封装成函数生成数据。拿 python 为例,可以自己结合 random() 之类的函数随机生成数据,还可以使用 faker( 项目地址 )这样的第三方库来实现:

这类生成测试数据的方法试用的场景是,对数据本身的值不关心但是测试中又必须需要这些参数的情况。

综合运用上述方法准备测试数据

在实际工作中,很少使用单一的方法就能满足测试的需求,往往是综合运用上述各种方法、一个典型的应用场景是,通过 API 生成最基础的测试数据,比如车辆的 vid,然后使用数据库和 MQ 的方法是生成符合测试需要的车辆状态数据。

我以上报车辆状态数据的测试为例子,来分享一下具体的如何将 API 调用和 MQTT 协议的方法结合起来构造测试数据。

比如我们要测试云端对车辆上报的报警数据的处理是否符合要求。首先,我们需要通过调用注册 Vechile ID 的接口来注册一台车并且获得车辆证书,通过调用这个接口我们可以得到一个车辆的 ID 以及证书数据。再结合 MQTT 协议产生车辆的报警数据。

为了构造测试数据的更加便捷,我们往往是对上面的操作进行封装。用封装后的方法产生测试数据。

02 准备测试数据的时机

前面介绍了准备测试数的方法,那么应该在什么时候创建好所需要的测试数据。是在测试用例执行中创建测试数据(On-the-Fly 方法)还是在测试执行前就准备好测试数据(Out-of-Box 方法)。

其实,创建测试数据的时机要根据实际的需要来。主要参考一下几个因素:

  1. 创建测试数据所需要的时间。如果创建数据需要花很长时间,那么最好采用 Out-of-Box 方法,在测试执行之前就准备好,以减少整个测试执行的时间。

  2. 测试数据是否需要经常变动。如果测试数据不需要经常变动,那么最好采用 Out-of-Box 方法。如果事先生成数据在测试用例中会失效,比如具有有效期的数据,那么就适合采用 On-the-Fly 方法,在测试执行中创建。

  3. 测试数据是否存在于很多系统。如果测试数据需要在很多系统中都要创建各自的部分,各自又有很多依赖关系,那么就适合 Out-of-Box 方法。因为在测试用例执行中创建,会导致测试代码比较臃肿,不够清晰。

  4. 构造测试数据的服务是否稳定。在不太稳定的服务中构造测试数据,会产生大量构造测试数据失败的情况。这种情况下采用 Out-of-Box 方法还是比较明智的。

接下来,我们详细看一下 On-the-Fly 方法和 Out-of-Box 方法各自的特点,以及适用场景。

实时创建(On-the-Fly)

实时生成测试数据的方法,指的是在测试用例代码执行过程中即时创建测试数据。比如,测试车辆驾驶中,不能执行远程控制命令的场景。在测试执行中,可以通过封装的 MQ 方法设置测试车辆的车辆状态处于驾驶中,接下来就可以测试远程执行命令了。

On-the-Fly 方法创造的测试数据通常是对每一个测试用例起作用的,不同的测试用例都有自己专属的测试数据。像这种车辆状态数据就适合采用 On-the-Fly 方法创造,这种状态数据通常是每个测试用例都不同。这种构造测试数据的好处是,避免测试数据在测试用例执行前被修改而产生非预期的测试结果。这样的测试数据使用完之后,通常在测试用例结束之后,恢复成原始数据,避免影响其他测试用例。

在自动化测试发展早期,测试实践中通常都会这种方法,也是比较好的方法。他解决了测试用例之间数据之间干扰的问题,也避免了测试完之后的脏数据问题。但是随着软件架构的发展,以及测试频率的提高,这种方式的弊端也逐渐显示出来了,主要有以下几个方面: 首先,有的测试数据比较耗时。 在测试用例执行过程中实时创建测试数据,会导致测试用例执行的时间被拉长。如果测试用例特别多,测试频率又特别高,那么测试时间就变得特别长,这显然不适合现在互联网软件的迭代节奏。为了解决测试耗时的问题,可以采用 Out-of-Box 方法。 其次,测试数据本身之间复杂的关联性导致构造困难。 很多时候,你为了测试某一个场景,需要构造一堆相关联的测试数据,也是偏向业务链后台的测试数据,这个问题越明显。

比如,要测试被授权人对车执行远程控制命令的场景。会需要车主账号、被授权人账号、车辆 ID、车辆 ID 与车主账号绑定,车主给被授权人授权车辆等前置数据。如果在测试用例执行中准备这些测试数据,那肯定是崩溃的。如果每一个测试用例都这么做,一定会导致测试时间变得非常长。为了解决这个问题,可以考虑将一部分稳定的数据事先创建好,比如车主账号、被授权人账号、车辆 ID 以及授权关系等数据。

微服务架构的流行导致成功生成测试数据的稳定性降低现在大量互联网应用采用微服务架构,不同功能划分为更多的微服务独立开发和部署,很多时候测试环境里面,这些微服务并不是 100% 可用的。也就是说,不是任何时候构造测试数据都能成功。比如你测试的微服务 B,需要依赖微服务 A 构造数据,而这时候正好微服务 A 不可用,这就 block 了微服务 B 的测试。

为了解决上面的问题,事先准备测试数据的 Out-of-Box 方法,就有了用武之地。

提前准备(Out-of-Box)

Out-of-Box 方法,指的是在测试用例执行前,就已经准备好了所用的全部或者部分测试数据,而不是在测试用例中实施创建。因此,执行测试用例时候,可以节省不少准备测试数据的时间,同时也避免因为依赖的测试数据准备服务不可用导致测试被 block 的情况。

那么 Out-of-Box 方法是否也存在缺点呢?

最主要的问题是 “有效性” 问题,就是有测试执行中发现测试数据不可用的风险。比如,测试被授权人远程执行车控命令的场景,当你执行测试时,发现被授人的身份已经被车主账号删掉了,这样就导致测试用例执行失败,也就不能顺利完成测试了。

由此可见,这些实现创建好的测试数据,有可能在测试用例执行时已经不可用了,因为这些数据有可能已经进行了非预期的修改。比如,在其他测试用例执行时,使用了这个测试数据,并修改了这些数据的状态。

为了解决这个问题,我们通常采用优化测试管理流程,让不同的测试人员、测试业务都有自己独立的测试数据,并且统计在 confluence、jira 或者其他公共平台上,大家严格遵守,不要乱用测试数据。

另外,Out-of-Box 方法不适合准备,只能被使用一次的测试数据,只会使用一次的测试数据还是采用 On-the-Fly 方法准备比较合适。

实际工作中,我们通常是采用 On-the-fly 和 Out-of-box 这两种方式相结合的方式来准备测试数据。我们可以根据测试目的的不同,将测试数据划分为 “固定数据” 和 “易变数据”。比如某些测试场景中,车辆 ID、车辆 Profile、车主账号等信息是相对稳定、不经常变化的数据,那么我们可以将这些测试数据称为 “固定数据”,这类数据适合采用 Out-of-box 方式创建。但是在某些测试场景中,比如车辆 ID 的注销,车辆 Profile 的变更测试,那么车辆 ID、车辆 Profile 就不能叫做 “固定数据” 而是应该叫做 “灵活数据”,这类数据适合采用 On-the-fly 方式准备。

综合运用这两类方法,可以满足大部分测试数据准备的场景。可以解决准备测试数据耗时长、准备测试数据成功率不高等问题。

03 构造测试数据的痛点及应对

前面,我们分析了两种准备测试数据的时机以及各自的优缺点。那么我们实际工作中,准备测试数据的工作有哪些痛点,我们又该如何解决呢?

调用封装函数的复杂性

前面提到封装 API 到一个函数,然后调用这个函数来构造测试数据的方法。但是这种封装方式会有问题,就是如果参数非常多,那么你调用它来构造数据时,就要准备这些参数。如果这些参数是基本类型的话还好,如果参数本身也是对象的话,可能就会更加麻烦了,因为你要创建这些对象。而创建这些对象,有可能要继续调用其他封装的函数,从而牵连出一系列函数调用的操作。

比如,调用这样一个封装了注册车辆 vid 的函数:

由此可见,每次使用封装的函数准备测试数据时,我们要给函数传递所有的参数。其实大多数测试场景下,所有参数都可以给一个默认值,用这个函数准备测试数据时,只需要给那些有明确要求的参数传值,其他参数保持默认值即可。这样封装的函数就变成这样:

这样,大大减少了调用封装函数的成本。当测试用例中只需要一个特定 vin 的车辆时,只需要给 register_vehicle 传递参数 vin 的值,其他的测试用例不关心的参数都可以保持默认值。

封装函数的版本管理

通常我们封装函数是给所有的测试项目共同使用的,这样才能最大化封装函数的价值。共享封装函数的办法通常是将其打包,然后在其他项目中引用。如果你的测试项目是使用 Python,那么可以将封装的函数用 setuptools 打包上传到公司的 Pypi 平台,在测试的项目中用 pip 安装。如果你的测试是使用 Java,可用 Maven 将封装函数打成 Jar 包并上传到公司的私有仓库,在测试项目中的 pom.xml 中引入 jar 包就好了。

现在的互联网应用版本迭代更新特别快,导致封装函数也要对应的迭代更新。这就会产生数据准备函数的包升级更新比较频繁,包的版本号就要随着变化。所以引用了这些数据准备函数包的项目,就要更新包的版本。给使用者带来了一些麻烦。

为了解决项目对封装函数的依赖问题,我们可以将其做成 Restful API,这样使用者就免去了频繁更新这些依赖包的麻烦。而且 Restful API 天生的跨平台支持,让调用方不管是用 Java 写测试用例还是 Python 写测试用例,都可以得到完美的支持。接下来我们就详细介绍一下基于 Restful API 的测试数据准备方案。

04 统一测试数据生成平台

前面介绍了创建测试数据的主要方法、创建测试数据的时机,以及测试数据生成中的痛点。随着测试技术的发展,测试数据准备技术与架构也需要逐步进化,来满足互联网微服务架构的发展趋势以及快速迭代的特点。

现在业界,将测试数据准备的工作进行平台化,逐渐成为测试数据准备方案的发展方向。而 Restful API 的测试数据准备方案,正好适合平台化的发展方向。我们可以将基于 Java 开发的数据准备函数用 Spring Boot 包装成 Restful API,或者将基于 Python 开发的数据准备函数用 Flask 或者 Django REST framework 包装成 Restful API。

这样一来,测试人员可以通过 Restful API 调用来准备测试数据了,由于 HTTP 协议是跨平台的,所以几乎所有的测试框架都可以直接使用这些 Restful API 准备测试数据。由于使用 Restful API 提供测试数据,这样方便我们将提供各类测试数据的服务整合到一起,形成 “统一测试数据生成平台”。结合 Swagger 提供的界面化文档,可以方便看到接口调用的方法,并且可以直接在界面上调用接口生成数据。既满足自动化测试的需要,也能满足手工测试的需求。

目前为止,我们将测试数据准备工作进行了服务化,下图就是一个统一测试数据生成平台的 Restful API 界面。

统一测试数据平台 Restful API UI 界面

" 统一测试数据平台 " 从提供的测试数据特性来分,可以分为真实数据和 Mock 数据。真实数据就是封装微服务的接口,在业务系统中实际产生真实的业务数据用作测试数据。Mock 数据是指通过 mock 技术产生非实际业务中的数据、这类数据一般用于解决服务依赖问题。

提供真实数据

下面通过 Flask Web 框架来介绍如何通过封装业务操作提供真实的测试数据实践。

比如,我要测试远程控制车辆的 API,其中有一个测试用例是验证在车辆在行驶中时不能进行远程控制。针对这个测试用例,我们需要的测试数据有被控制车辆的 ID 以及车辆状态。下面以准备车辆 ID 的 Restful API 为例,介绍具体的实现方式。

首先,使用 pipenv 创建虚拟环境,安装好 Flask 框架。

下面这段代码是封装了业务接口 api/1/in/vehicle/profile 的代码片段。

在这段代码中,提供默认参数,只需要传递测试感兴趣的参数就可以,不感兴趣的数据保持默认值即可。结合 flasgger 提供的 swag_from 装饰器,给接口编写文档,让封装的接口易懂和易用。

提供 Mock 数据

什么情况下需要 Mock 数据,比如:服务 A 调用服务 B 的 Restful API,传递给服务 B 数据,服务 B 会根据数据情况返回给服务 A 一个 ACK 值。当服务 B 没有 Ready 时候,我们就需要模拟服务 B 的行为。

其实产生这种 Mock 数据,与前面介绍的产生实际业务数据,方法上并没有不同。只是在 Restful API 的 response 构造上,前者构造产生的数据来自与真实的业务接口,而 Mock 的数据是根据测试需求伪造的。模拟前面提到的服务 B 的行为,以 Flask 方案为例,就是:

通过控制上面 ack 的内容,就可以模拟服务 B 的各种 Response 了。如果将这个 ack 的内容存入数据库中,让 get_ack 函数从数据库中取得 ack 并返回。再写一个接口用于往数据库中写入 ack,那么就可以在自动化测试中随意控制 ack 的返回内容了。

05 总结

本篇文章我们梳理了测试数据准备的各种方法,并分析了各自的优缺点及适用场景。测试数据准备的时机上看,对于不常改变的数据适合采用提前准备的方法,对于经常变化的数据在测试用例中准备更好。对测试工作中数据准备的痛点进行了剖析并给出了应对方案。最后,给出了解决测试数据生成痛点的终极解决方案——" 统一测试数据生成平台 "。


原文转自:TesterHome