文章内容来源于《软件测试52讲》
测试数据的准备是软件测试过程中非常重要的一个环节,无论是手工测试,还是自动化测试,无论是 GUI 测试,还是 API 测试,无论是功能测试,还是性能测试,都避不开测试数据准备的工作。
从创建测试数据的维度来看,测试数据准备方法主要可以分为四类:
一、基于 GUI 操作生成测试数据
最原始的创建测试数据的方法,采用 E2E(end 2 end) 的方式来执行业务场景,然后生成数据的方法。
比如,想要测试用户登录功能,那么首先就要准备一个已经注册的用户
方法:直接通过 GUI 界面来注册一个新用户,然后用这个新创建的用户完成用户登录功能的测试。
优点:简单直接,在技术上没有任何复杂性,而且所创建的数据完全来自于真实的业务流程,可以最大程度保证数据的正确性。
缺点:
1、创建测试数据的效率非常低
- 每次执行 GUI 业务操作都只能创建一条数据
- 基于 GUI 操作的执行过程比较耗时
2、基于 GUI 的测试数据创建方法不适合封装成测试数据工具
由于测试数据的创建是通过 GUI 操作实现的,所以把这种数据创建方法封装成测试数据准备工具的过程,其实就是在开发 GUI 自动化测试用例。
无论是从开发工作量,还是从执行效率来讲,把基于 GUI 操作的测试数据创建方法封装成测试数据准备工具都不是最佳的选择。
3、测试数据成功创建的概率不会太高
测试数据准备的成功率受限于 GUI 自动化执行的稳定性,而且任何界面的变更都有可能引发测试数据创建的失败。
4、会引入不必要的测试依赖
比如,被测对象是用户登录功能,通过 GUI 页面操作准备这个已经注册的用户,就首先要保证用户注册功能没有问题,而这显然是不合理的。
应用场景:
帮助我们找到创建一个测试数据的过程中,后端调用了哪些 API,以及修改了哪些数据库的业务表,是“通过 API 调用生成测试数据”,以及“通过数据库操作生成测试数据”这两种方法的基础。
二、通过 API 调用生成测试数据
目前主流的测试数据生成方法
其实,当我们通过操作 GUI 界面生成测试数据时,实际的业务操作往往是由后端的 API 调用完成的。所以,我们完全可以通过直接调用后端 API 生成测试数据。
还是以用户登录功能的测试为例,我们通过 GUI 界面注册新用户时,实际上就是调用了 createUser 这个 API。
既然知道了具体要调用哪个 API,那么我们就可以跳过在 GUI 界面的操作,直接调用 createUser 生成“已经注册的用户”这个测试数据了。
为了规避在创建测试数据时过于在乎实现细节的问题,在实际工程实践中,我们往往会把调用 API 生成测试数据的过程封装成测试数据准备函数。
那么问题来了,怎么才能知道前端新用户注册这个操作到底调用了哪些后端 API 呢?推荐三种方式:
- 直接询问开发人员(最直接)
- 如果有一定的代码基础,可以直接阅读源代码(作为直接询问方法的补充)
- 在一个你可以独占的环境上执行 GUI 操作创建测试数据,与此同时监控服务器端的调用日志,分析这个过程到底调用了哪些 API
优点:
- 可以保证创建的测试数据的准确性(使用了和 GUI 操作同样的 API 调用)
- 测试数据准备的执行效率更高(该方法跳过了耗时的 GUI 操作)
- 把创建测试数据的 API 调用过程,封装成测试数据函数更方便(这个调用过程的代码逻辑非常清晰)
- 测试数据的创建可以完全依赖于 API 调用
当创建测试数据的内部逻辑有变更时,由于此时 API 内部的实现逻辑也会由开发人员同步更新,所以我们依旧可以通过调用 API 来得到逻辑变更后的测试数据,而这个过程对使用来说是完全透明的。
缺点:
- 并不是所有的测试数据创建都有对应的 API 支持。
也就是说,并不是所有的数据都可以通过 API 调用的方式创建,有些操作还是必须依赖于数据库的 CRUD 操作。那么,这时,我们就不得不在测试数据准备函数中加入数据库的 CRUD 操作生成测试数据了。 - 有时,创建一条业务线上的测试数据,往往需要按一定的顺序依次调用多个 API,并且会在多个 API 调用之间传递数据,这也无形中增加了测试数据准备函数的复杂性。
- 虽然相比于 GUI 操作方式,基于 API 调用的方式在执行速度上已经得到了大幅提升,并且还可以很方便地实现并发执行(比如,使用 JMeter 或者 Locust),但是对于需要批量创建海量数据的场景,还是会力不从心。
三、通过数据库操作生成测试数据
目前主流的测试数据生成方法
常见的做法是,将创建数据需要用到的 SQL 语句封装成一个个的测试数据准备函数,当我们需要创建数据时,直接调用这些封装好的函数即可。
还是以用户登录功能测试为例,当我们通过 GUI 界面注册新用户时,实际上是在后端调用了 createUser 这个 API,而这个 API 的内部实现逻辑是,将用户的详细信息插入到了 userTable 和 userRoleTable 这两张业务表中。
那么此时,我们就可以直接在 userTable 和 userRoleTable 这两张业务表中插入数据,然后完成这个新用户的注册工作。
这样做的前提是:需要知道前端用户通过 GUI 操作注册新用户时,到底修改了哪些数据库的业务表
推荐三种方式:
- 直接向开发人员索要使用到的 SQL 语句;
- 直接阅读产品源代码;
- 在一个你可以独占的环境上执行 GUI 操作产生测试数据,与此同时,监控独占环境的数据库端业务表的变化,找到哪些业务表发生了变化。
优点:
测试数据的生成效率非常高,可以在较短的时间内创建大批量的测试数据。
缺点:
- 很多时候,一个前端操作引发的数据创建,往往会修改很多张表,因此封装的数据准备函数的维护成本要高得多
数据库,大部分业务的数据库表都是关联的,如果改不好,可能定位问题就需要很长时间 - 容易出现数据不完整的情况
比如一个业务操作,实际上在一张主表和一张附表中插入了记录,但是基于数据库操作的数据创建可能只在主表中插入了记录,这种错误一般都会比较隐蔽,往往只在一些特定的操作下才会发生异常; - 当业务逻辑发生变化时,即 SQL 语句有变化时,需要维护和更新已经封装的数据准备函数。
四、综合运用 API 和数据库的方式生成测试数据
目前,在实际的工程实践中,很少使用单一的方法生成测试数据,基本都是采用 API 和数据库相结合的方式。
最典型的应用场景是,先通过 API 调用生成基础的测试数据,然后使用数据库的 CRUD 操作生成符合特殊测试需求的数据
以创建用户为例:
假设,我们需要封装一个创建用户的函数,这个函数需要对外暴露“用户国家”和“支付方式”这两个参数。由于实际创建用户是通过后台 createUser API 完成的,但是这个 API 并不支持指定“用户国家”和“支付方式”,所以我们就需要自己封装一个创建用户的函数。
思路:
1、调用 createUser API 完成基本用户的创建
2、调用 paymentMethod API 实现用户对于不同支付方式的绑定
(其中 paymentMethod API 使用的 userID 就是上一步中 createUser API 产生的用户 ID)
3、通过数据库的 SQL 语句更新“用户国家”。
注:在这个例子中,createUser API 和 paymentMethod API 只是为了说明如何综合运用 API 的顺序调用
延伸:
目前,我们需要创建的测试数据并不仅仅局限于数据库,很多时候还需要创建消息队列里面的数据。