38 | 测试数据的“银弹”- 统一测试数据平台(下)

你好,我是茹炳晟,今天我分享的主题是:“测试数据的“银弹”之统一测试数据平台(下)”。

在上一篇文章中,我和你分享了测试数据准备1.0时代的实践,在这个1.0时代,测试数据准备的最典型方法是,将测试数据准备的相关操作封装成数据准备函数。今天,我将继续为你介绍测试数据准备的2.0和3.0时代的实践,看看创建测试数据的方法,又发生了哪些变革。

在1.0时代,为了让数据准备函数使用更方便,避免每次调用前都必须准备所有参数的问题,我和你分享了很多使用封装函数隐藏默认参数初始化细节的方法。

但是,这种封装函数的方式,也会带来诸如需要封装的函数数量较多、频繁变更的维护成本较高,以及数据准备函数JAR版本升级的尴尬。所以,为了系统性地解决这些可维护性的问题,我们对数据准备函数的封装方式做了一次大变革,也由此进入了测试数据准备的2.0时代。

测试数据准备的2.0时代

在测试数据准备的2.0时代,数据准备函数不再以暴露参数的方式进行封装了,而是引入了一种叫作Builder Pattern(生成器模式)的封装方式。这个方式能够在保证最大限度的数据灵活性的同时,提供使用上的最大便利性,并且维护成本还非常低。

事实上,如果不考虑跨平台的能力,Builder Pattern可以说是一个接近完美的解决方案了。关于什么是“跨平台的能力”,我会在测试数据准备的3.0时代中解释,这里先和你介绍我们的主角:Builder Pattern。

Builder Pattern是一种数据准备函数的封装方式。在这种方式下,当你需要准备测试数据时,不管情况多么复杂,你一定可以通过简单的一行代码调用来完成。听起来有点玄乎?没关系,看完我列举的这些实例,你马上就可以理解了。

实例一:你需要准备一个用户数据,而且对具体的参数没有任何要求。也就是说,你需要的仅仅是一个所有参数都可以采用默认值的用户。那么,在Builder Pattern的支持下,你只需要执行一行代码就可以创建出你需要的这个所有参数都是默认值的用户了。这行代码就是:

UserBuilder.build();

实例二:你现在还需要一个用户,但是这次需要的是一个美国的用户。那么这时,在Builder Pattern的支持下,你只用一行代码也可以创建出这个指定国家是美国,而其他参数都是默认值的用户。这行代码就是:

UserBuilder.withCountry("US").build();

实例三:你又需要这样一个用户数据:英国用户,支付方式是Paypal,其他参数都是默认值。那么这时,在Builder Pattern的支持下,你依然可以通过一行简单的代码创建出满足这个要求的用户数据。这行代码就是:

UserBuilder.withCountry("US").withPaymentMethod("Paypal").build();

通过这三个实例,你肯定已经感受到,相对于1.0时代的通过封装函数隐藏默认参数初始化的方法来说,Builder Pattern简直太便利了。

趁热打铁,我再来和你总结一下Builder Pattern的便利性吧:

  • 如果仅仅需要一个全部采用缺省参数的数据的话,你可以直接使用TestDataBuilder.build()得到;
  • 如果你对其中的某个或某几个参数有特定要求的话,你可以通过“.withParameter()”的方式指定,而没有指定的参数将自动采用默认值。

这样一来,无论你对测试数据有什么要求,都可以以最灵活和最简单的方式,通过一行代码得到你要的测试数据。

在实际工程项目中,随着Builder Pattern的大量使用,又逐渐出现了更多的新需求,为此我归纳总结了以下4点:

  • 有时候,出于执行效率的考虑,我们不希望每次都重新创建测试数据,而是希望可以从被测系统的已有数据中搜索符合条件的数据;
  • 但是,还有些时候,我们希望测试数据必须是全新创建的,比如需要验证新建用户首次登录时,系统提示修改密码的测试场景,就需要这个用户一定是被新创建的;
  • 更多的时候,我们并不关心这些测试数据是新创建的,还是通过搜索得到的,我们只希望以尽可能短的时间得到需要的测试数据;
  • 甚至,还有些场景,我们希望得到的测试数据一定是来自于Out-of-box的数据。

为了能够满足上述的测试数据需求,我们就需要在Builder Pattern的基础上,进一步引入Build Strategy的概念。顾名思义,Build Strategy指的是数据构建的策略。

为此,我们引入了Search Only、Create Only、Smart和Out-of-box这四种数据构建的策略。这四类构建策略在Builder Pattern中的使用很简单,只要按照以下的代码示例指定构建策略就可以了:

UserBuilder.withCountry(“US”).withBuildStrategy(BuildStrategy.SEARCH_ONLY.build();
UserBuilder.withCountry(“US”).withBuildStrategy(BuildStrategy.CREATE_ONLY).build();
UserBuilder.withCountry(“US”).withBuildStrategy(BuildStrategy.SMART).build();
UserBuilder.withCountry(“US”).withBuildStrategy(BuildStrategy.OUT_OF_BOX).build();

结合着这四类构建策略的代码,我再和你分享一下,它们会在创建测试数据时执行什么操作,返回什么样的结果:

  • 当使用BuildStrategy.SEARCH_ONLY策略时,Builder Pattern会在被测系统中搜索符合条件的测试数据,如果找到就返回,否则就失败(这里,失败意味着没能返回需要的测试数据);
  • 当使用BuildStrategy.CREATE_ONLY策略时,Builder Pattern会在被测系统中创建符合要求的测试数据,然后返回;
  • 当使用BuildStrategy.SMART策略时,Builder Pattern会先在被测系统中搜索符合条件的测试数据,如果找到就返回,如果没找到就创建符合要求的测试数据,然后返回;
  • 当使用BuildStrategy.OUT_OF_BOX策略时,Builder Pattern会返回Out-of-box中符合要求的数据,如果在Out-of-box中没有符合要求的数据,build函数就会返回失败;

由此可见,引入Build Strategy之后,Builder Pattern的适用范围更广了,几乎可以满足所有的测试数据准备的要求。

但是,不知道你注意到没有,我们其实还有一个问题没有解决,那就是:这里的Builder Pattern是基于Java代码实现的,如果你的测试用例不是基于Java代码实现的,那要怎么使用这些Builder Pattern呢?

在很多大型公司,测试框架远不止一套,不同的测试框架也是基于不同语言开发的,比如有些是基于Java的,有些是基于Python的,还有些基于JavaScript的。而非Java语言的测试框架,想要使用基于Java语言的Builder Pattern的话,往往需要进行一些额外的工作,比如调用一些专用函数等。

我来举个例子吧。对于JavaScript来说,如果要使用Java的原生类型或者引用的话,你需要使用Java.type()函数;而如果要使用Java的包和类的话,你就需要使用专用的importPackage()函数 和 importClass() 函数。

这些都会使得调用Java方法很不方便,其他语言在使用基于Java的Builder Pattern时也有同样的问题。

但是,我们不希望、也不可能为每套基于不同开发语言的测试框架都封装一套Builder Pattern。所以,我们就希望一套Builder Pattern可以适用于所有的测试框架,这也就是我在前面提到的测试准备函数的“跨平台的能力”了。

为了解决这个问题,测试数据准备走向了3.0时代。

测试数据准备的3.0时代

为了解决2.0时代跨平台使用数据准备函数的问题,我们将基于Java开发的数据准备函数用Spring Boot包装成了Restful API,并且结合Swagger给这些Restful API提供了GUI界面和文档。

这样一来,我们就可以通过Restful API调用数据准备函数了,而且由于Restful API是通用接口,所以只要测试框架能够发起http调用,就能使用这些Restful API。于是,几乎所有的测试框架都可以直接使用这些Restful API准备测试数据。

由此,测试数据准备工作自然而然地就发展到了平台化阶段。我们把这种统一提供各类测试数据的Restful API服务,称为“统一测试数据平台”。

最初,统一测试数据平台就是服务化了数据准备函数的功能,并且提供了GUI界面以方便用户使用,除此以外,并没有提供其他额外功能。如图1所示就是统一测试数据平台的UI界面。

图1 最初的统一测试数据平台UI界面

后来,随着统一测试数据平台的广泛使用,我们逐渐加入了更多的创新设计,统一测试数据平台的架构也逐渐演变成了如图2所示的样子。

图2 演变后的统一测试数据平台架构

接下来,我和你分享一下统一测试数据平台的架构设计中最重要的两个部分:

  1. 引入了Core Service和一个内部数据库。其中,内部数据库用于存放创建的测试数据的元数据;Core Service在内部数据库的支持下,提供数据质量和数量的管理机制。

  2. 当一个测试数据被创建成功后,为了使得下次再要创建同类型的测试数据时可以更高效,Core Service会自动在后台创建一个Jenkins Job。这个Jenkins Job会再自动创建100条同类型的数据,并将创建成功的数据的ID保存到内部数据库,当下次再请求创建同类型数据时,这个统一测试数据平台就可以直接从内部数据库返回已经事先创建的数据。
    在一定程度上,这就相当于将原本的On-the-fly转变成了Out-of-box,缩短整个测试用例的执行时间。当这个内部数据库中存放的100条数据被逐渐被使用,导致总量低于20条时,对应的Jenkins Job会自动把该类型的数据补足到100条。而这些操作对外都是透明的,完全不需要我们进行额外的操作。

这就是测试数据准备的3.0时代的最佳实践了。关于这个统一测试数据平台,如果你还想了解更多的技术细节,欢迎你给我留言,我们一起讨论。

总结

我和你分享了测试数据准备2.0时代的Builder Pattern实践,以及3.0时代的统一测试数据平台。

2.0时代的Builder Pattern在提供了最大限度的数据灵活性的同时,还保证了使用上的最大便利性,并且维护成本还非常低。如果不考虑跨平台能力的话,Builder Pattern已经是一个接近完美的解决方案了。

3.0时代统一测试数据平台,其实是将所有的数据准备函数在Spring Boot的支持下转变为了Restful API,为跨平台和跨语言的各类测试框架提供了统一的数据准备方案。

思考题

关于统一测试数据平台,由于引入了Core Service和内部数据库,所以可以在此基础上实现更多的高级功能。对此,你觉得还可以引入哪些功能呢?

感谢你的收听,欢迎你给我留言。

精选留言

  • Joie

    2018-09-26 13:54:18

    好激动,老师说到的3.0是我在项目中自己摸索总结出来的,上个月已经投产使用,目前在进行优化中,将数据自动生成,case的一些规则都进行分层处理。很棒,方向是对的,继续努力。
    作者回复

    赞👍这个的确就是数据准备的大方向,尤其规模大了之后更显的关键

    2018-10-05 08:37:15

  • Robert小七

    2018-09-24 07:52:34

    怎么觉得最近的文章都是普及概念了
    作者回复

    很多时候概念本身比会使用工具来得重要的多,对于测试数据准备的文章中介绍的很多方法和理念都是外面找不到的,都是来自于大项目中的工程实践,如果大家对工具本身的使用更感兴趣,我还是建议通过官方文档进行学习,但是怎么找到适合你的工具,以及学习这些工具设计的思路,还是要能够掌握原理。

    2018-09-26 14:06:36

  • 咕咕咕

    2019-10-22 22:19:36

    非常感谢老师的分享,确实都是干货,但是鉴于自己是小白,很多东西都只是有了一个懵懂的概念。以后能够实际实践并且弄明白的时候再回来打卡。
  • Geek_723829

    2019-01-09 14:15:51

    内部数据库存储的是创建的同类型数据,还是存储的是同类型数据的ID而已,再到真实数据库中取?
    作者回复

    存储的是id和其他用于筛选数据的相关字段,实际的数据还是在真正的被测系统的数据库中的

    2019-03-02 20:00:42

  • coco張

    2020-08-18 22:09:38

    原来我们在用的已经是3.0版本了,很庆幸有个非常给力的tools团队
  • 万历十五年

    2020-10-21 16:32:11

    计算机世界的一大法宝:遇到问题加一层,再结合设计模式,来解决数据准备的问题: 数据准备1.0时代是加一层模板模式的封装函数,2.0时代是builder pattern来封装函数,3.0时代是以saas方式封装成api。从1.0时代到3.0时代的这种渐进式的演进,都是要解决当时项目面临的实际问题。
  • 捷后愚生

    2020-08-05 22:44:22

    有收获!学习文章后,接触了新的概念:Builder Pattern(生成器模式)、Build Strategy、跨平台的能力、统一测试数据平台,虽然自己现在工作还没有要做这样的工作,但是现在知道了概念,以后有需要,就能更加快速想到解决问题的思路。这些问题都是自己从来没有思考过的,阅读本篇文章是大开眼界了。
  • SugarZh

    2018-12-16 18:04:51

    Build Pattern确实实现了统一封装,方便了调用;但是如果系统业务发生调整,对应的测试数据肯定就要发生改变,那么还是要从新修改Build Pattern底层的实现逻辑;这样的工作也不小啊;
    毕竟对于纯互联网公司,业务随时可能发生变化,对应的测试数据的变化,也会带来一定的麻烦。
  • silver_mango

    2018-12-04 22:16:04

    从移动大会的ppt演讲一直追到这里,受益匪浅。思路非常重要。感谢老师!
    作者回复

    哈哈,感谢支持,有问题随时交流

    2018-12-11 08:42:56

  • 朝如青丝暮成雪

    2019-05-10 12:09:07

    最近数据这块出现了问题,回头有读了一遍。理解了测试数据2.0时代,但是到3.0就不太懂了,我目前的理解是:把准备数据函数抽象成接口,和swagger链接,这样直接通过swagger的图形界面输入要测试的数据,把测试数据写入数据库。是这样吗?麻烦老师回答一下。
    作者回复

    不完全是,swagger只是提供一个界面方便测试和gui,并不会影响3.0自己的逻辑,3.0强调的是服务化

    2019-05-13 07:52:26

  • 楚耳

    2019-02-19 18:12:11

    老师你文中提到,Core Service 会自动在后台创建一个 Jenkins job 当内部数据库少于20条数据时,自动补足数据,这个内部数据库的数据是用一条就删一条嘛,不然怎么知道只有20条是有效的数据, 还有这个job是循环去扫描数据库嘛,查看是否少于20条数据
  • liangce

    2019-02-17 17:45:59

    赞一个,3.0版的核心功能大致已然了解。有好几个问题想继续请教一下:
    1.测试数据的元数据具体指的是?
    2.架构图中非核心部分
    2.1 架构图中后面涉及到的“testdata preparation tool”指的是封装了数据制造api的sdk?
    2.2 还有 “testdata validation tool” 指的又是什么?用处是什么?
    3.数据质量和数量的管理机制,用哪些指标来度量数据质量?有一些什么样的管理机制?(沉淀有共性的基础数据、服务?下线失败率高的服务?清理历史无用的数据?)
    4.关于多环境的问题
    (开发环境、测试环境(功能、性能、异常)、预发布、线上),我本以为所有服务都有“环境”这个参数来控制agent我看您的回答中也提到有“数据版本”,版本是针对服务的、还是testcase、还是?能再详细一些介绍一下么?
  • rachel

    2018-09-25 17:40:41

    思路挺好。但是感觉是虚的、不落地。有源码供研究或更详细的框架设计思路就好了。
    作者回复

    实际项目已经投入使用,但是由于不开源,无法提供源代码

    2018-09-26 14:02:48

  • 蜜拉

    2018-09-25 15:27:50

    请问,Builder Pattern内部还是操作API或者数据库来造数据的嘛?
    作者回复

    是的,内部实现还是数据准备函数,只是在此基础上加了一层易用性的封装

    2018-09-26 14:02:00

  • 海朋森

    2020-08-05 13:16:16

    测试数据准备的服务化,其实,是json schema的逆向
  • Geek_p79wqo

    2019-10-29 10:41:15

    Builder Pattern 这种模式用其他语言实现就好吧 里面没问题 只是有些时候 人少 真的拉不起来平台化🤦‍♀️
  • 口水窝

    2019-05-15 14:42:22

    在我学习本篇文章后,对于3.0的看法就是,包装了一套能够顾兼容Java、Python和JavaScript框架的2.0版本,且一直在内部数据库中存储着100条待用的数据,这就减少了数据准备时间。但是基于我对Restful接口和SpringBoot框架不了解,对于3.0的原理还是懵懂的,且后面有点余味尤尽、戛然而止的感觉,不知道茹老师可否进一步扩展呢?
  • 阿嬷

    2019-01-07 23:34:58

    如果使用pyrhon开发,是不是就不存在需要封装多个参数组合函数的问题?
    作者回复

    也是需要的,看你怎么来设计

    2019-01-08 12:56:55

  • 一池浮萍

    2018-09-25 22:23:10

    挺好的思路
    作者回复

    感谢支持

    2018-09-26 14:00:15

  • 小北

    2018-09-25 23:58:04

    Restful API 不推荐使用create 这样的动词命名。貌似应该用put get post 这些表示创建修改的动作。
    作者回复

    你说的对的,create指的是后台实际执行的操作,而restful api暴露的还是标准化的method

    2018-09-26 14:00:07