【软件测试】学习笔记-统一测试数据平台

这篇文章主要探讨全球大型电商企业中关于准备测试数据的最佳实践,从全球大型电商企业早期的测试数据准备实践谈起,分析这些测试数据准备方法在落地时遇到的问题,以及如何在实践中解决这些问题。其实,这种分析问题、解决问题的思路,也是推动着测试数据准备时代从1.0到2.0再到3.0演进的原因。

在这个过程中,跟着时代的演进,理解测试数据准备技术与架构的发展历程,并进一步掌握3.0时代出现的业内处于领先地位的“统一测试数据平台”的设计思路。

我们就先从数据准备的1.0时代谈起吧。

测试数据准备的1.0时代

其实,据我观察,目前很多软件企业还都处于测试数据准备的1.0时代。

这个阶段最典型的方法就是,将测试数据准备的相关操作封装成数据准备函数。这些相关操作,既可以是基于API的,也可以是基于数据库的,当然也可以两者相结合。

有了这些数据准备函数后,你就可以在测试用例内部以On-the-fly的方式调用它们实时创建数据,也可以在测试开始之前,在准备测试环境的阶段以Out-of-box的方式调用它们事先创建好测试数据。

那么,一个典型的数据准备函数长什么样子呢?我们一起来看看这段代码吧,里面的createUser函数,就是一个典型的数据准备函数了。

public static User createUser(String userName, String password, UserType userType, PaymentDetail paymentDetail, Country country, boolean enable2FA)
{//使用API调用的方式和数据库CRUD的方式实际创建测试数据 ...
}

乍一看,你可能觉得,如果可以将大多数的业务数据创建都封装成这样的数据准备函数,那么测试数据的准备过程就变成了调用这些函数,而无需关心数据生成的细节,这岂不是很简单、直观嘛。

但,真的是这样吗?

这里,我建议你在继续阅读后面的内容之前,先思考一下这个方法会有什么短板,然后再回过头来看答案,这将有助于加深你对这个问题的理解。当然,如果你已经在项目中实际采用了这个方法的话,相信你已经对它的短板了如指掌了。

好了,现在我来回答这个问题。利用这种数据准备函数创建测试数据方法的最大短板,在于其参数非常多、也非常复杂。在上面这段代码中,createUser函数的参数有6个。而实际项目中,由于测试数据本身的复杂性、灵活性,参数的数量往往会更多,十多个都是很常见的。

而在调用数据准备函数之前,你首先要做的就是准备好这些参数。如果这些参数的数据类型是基本类型的话,还比较简单(比如,createUser函数中userName、password是字符串型,enable2FA是布尔型),但这些参数如果是对象(比如,createUser函数的userType、paymentDetail和Country就是对象类型的参数)的话,就很麻烦了。为什么呢?

因为,你需要先创建这些对象。更糟糕的是,如果这些对象的初始化参数也是对象的话,就牵连出了一连串的数据创建操作。

下面这段代码,就是使用createUser函数创建测试数据的一个典型代码片段。

//准备createUser的参数
UserType userType = new UserType("buyer");
Country country = new Country("US");//准备createPaymentDetail的参数
PaymentType paymentType = new PaymentType("Paypal");
//调用createPaymentDetail创建paymentDetail对象
PaymentDetail paymentDetail = createPaymentDetail(paymentType,2000);//对主要的部分,调用createUser产生用户数据
User user=createUser(“TestUser001”, “abcdefg1234”, userType, paymentDetail, country, true);

由此可见,每次使用数据准备函数创建数据时,你都要知道待创建数据的全部参数细节,而且还要为此创建这些参数的对象,这就让原本看似简单的、通过数据准备函数调用生成测试数据的过程变得非常复杂。

那么,你可能会问,这个过程是必须的吗,可以用个某些技术手段“跳过”这个步骤吗?

其实,绝大多数的测试数据准备场景是,你仅仅需要一个所有参数都使用了缺省值的测试数据,或者只对个别几个参数有明确的要求,而其他参数都可以是缺省值的测试数据。

以用户数据创建为例,大多情况下你只是需要一个具有缺省(Default)参数的用户,或者是对个别参数有要求的用户。比如,你需要一个美国的用户,或者需要一个userType是buyer的用户。这时,让你去人为指定所有你并不关心的参数的做法,其实是不合理的,也没有必要。

为了解决这个问题,在工程实践中,就引入了如图1所示的封装数据准备函数的形式。

图1 数据准备函数的封装

在这个封装中,我们将实际完成数据创建的函数命名为createUserImpl,这个函数内部将通过API调用和数据库CRUD操作的方式,完成实际数据的创建工作,同时对外暴露了所有可能用到的user参数A、B、C、D、E。

接着,我们封装了一个不带任何参数的createDefaultUser函数。函数内部的实现,首先会用默认值初始化user的参数A、B、C、D、E,然后再将这些参数作为调用createUserImpl函数时的参数。

那么,当测试用例中仅仅需要一个没有特定要求的默认用户时,你就可以直接调用这个createDefaultUser函数,隐藏测试用例并不关心的其他参数的细节,此时也就真正做到了用一行代码生成你想要的测试数据。

而对于那些测试用例只对个别参数有要求的场景,比如只对参数A有要求的场景,我们就可以为此封装一个createXXXUser(A)函数,用默认值初始化参数B、C、D、E,然后对外暴露参数A。

当测试用例需要创建A为特定值的用户时,你就可以直接调用createXXXUser(A)函数,然后createXXXUser(A)函数会用默认的B、C、D、E参数的值加上A的值调用createUserImpl函数,以此完成测试数据的创建工作。

当然,如果是对多个参数有特定要求的场景,我们就可以封装出createYYYUser这样暴露多个参数的函数。

通过这样的封装,对于一些常用的测试数据组合,我们通过一次函数调用就可以生成需要的测试数据;而对于那些比较偏门或者不常用的测试数据,我们依然可以通过直接调用最底层的createUserImpl函数完成数据创建工作。可见,这个方法相比之前已经有了很大的进步。

但是,在实际项目中,大量采用了这种封装的数据准备函数后,还有一些问题亟待解决,主要表现在以下几个方面:

  1. 对于参数比较多的情况,会面临需要封装的函数数量很多的尴尬。而且参数越多,组合也就越多,封装函数的数量也就越多。
  2. 当底层Impl函数的参数发生变化时,需要修改所有的封装函数。
  3. 数据准备函数的JAR包版本升级比较频繁。由于这些封装的数据准备函数,往往是以JAR包的方式提供给各个模块的测试用例使用的,并且JAR会有对应的版本控制,所以一旦封装的数据准备函数发生了变化,我们就要升级对应JAR包的版本号。- 而这些封装的数据准备函数,由于需要支持新的功能,并修复现有的问题,所以会经常发生变化,因此测试用例中引用的版本也需要经常更新。

为了可以进一步解决这三个问题,同时又可以最大程度地简化测试数据准备工作,我们就迎来了数据准备函数的一次大变革,由此也将测试数据准备推向了2.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时代的最佳实践了。

总结

在1.0时代,准备测试数据最典型的方法就是,将测试数据准备的相关操作封装成数据准备函数。归纳起来,这个时代的数据准备函数,主要有两种封装形式:

  • 第一种是,直接使用暴露全部参数的数据准备函数,虽说灵活性最好,但是每次调用前都需要准备大量的参数,从使用者的角度来看便利性比较差;
  • 第二种是,为了解决便利性差的问题,我们引入了更多的专用封装函数,在灵活性上有了很大的进步,但是也带来了可维护差的问题。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/629025.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mathtype2024版本下载与安装(mac版本也包含在内)

安装包补丁主要是mathtype的安装包,与它的补丁。 详细安装过程: step1: 使用方法是下载完成后先安装MathType-win-zh.exe文件,跟着步骤走直接安装就行。 step2: 关闭之后,以管理员身份运行MathType7PJ.exe…

CF1178F2 Long Colorful Strip 题解 搜索

Long Colorful Strip 传送门 题面翻译 题目描述 这是 F 题的第二个子任务。F1 和 F2 的区别仅在对于 m m m 和时间的限制上 有 n 1 n1 n1 种颜色标号从 0 0 0 到 n n n,我们有一条全部染成颜色 0 0 0 的长为 m m m 的纸带。 Alice 拿着刷子通过以下的过…

一、ArcGIS Pro SDK for Microsoft .NET 开发环境配置

ArcGIS Pro二次开发需要的工具: 1.Visual Studio 2.ArcGIS Pro SDK 一、Visual Studio安装 经过查阅资料,ArcGIS Pro3.0版本需要安装Visual Studio2022版,因为只有22版的才会有有ArcGIS Pro3.0以上版对应ArcGIS Pro SDK,因此&…

如何编译openssl的早期版本的共享库,如openssl 1.0

背景介绍 最近在为客户排查问题的时候,发现客户提供的日志是加密的,解密工具依赖到了openssl 1.0的共享库。可是手头没有这么老版本的openssl共享库。因此只好手动编译一个出来。 编译步骤 因为openssl 1.0是比较老的版本,很多系统上的库已…

新能源汽车智慧充电桩解决方案:智慧化综合管理与数字化高效运营

一、方案概述 TSINGSEE青犀&触角云新能源汽车智慧充电桩解决方案基于管理运营平台,覆盖业务与应用、数据传输与梳理、多端开发、搭建等模块,融合AI、5G、Wi-Fi 、移动支付等技术,实现充电基础设施由数字化向智能化演进,通过构…

翻译: Pyenv管理Python版本从入门到精通一

你是否经常在管理系统上多个Python版本时遇到困难?这可能是一个艰巨的任务,尤其是在处理需要不同Python版本的不同项目时。 但别担心,有一个解决方案:pyenv。就像一个熟练的杂技演员,pyenv可以轻松处理多个Python版本…

连接超时的问题

连接超时的问题 通用第三方工具连接超时 connect timeout 方案一: /etc/ssh/sshd_config node1上操作,图是错的 方案二: windows上Hosts文件域名解析有问题 比如: 192.168.xx.100 node1 192.168.xx.161 node1 两个都解析成node…

绝地求生:【PC】未授权程序使用行为的相关公告

各位玩家大家好, 最近闲游盒通过PUBG玩家社区收到了关于未授权程序的举报,举报称有人在游戏内使用了能测量玩家间的距离并辅助迫击炮射击的未授权辅助程序。为此,我们想就该事项向大家进行如下公告: 使用此类未授权程序的行为违反…

23/76-LeNet

LeNet 早期成功的神经网络。 先使用卷积层来学习图片空间信息。 然后使用全连接层转换到类别空间。 #In[]LeNet,上世纪80年代的产物,最初为了手写识别设计from d2l import torch as d2l import torch from torch import nn from torch.nn.modules.loss import CrossEntropyLos…

工业平板定制方案_基于联发科、紫光展锐平台的工业平板电脑方案

工业平板主板采用联发科MT6762平台方案,搭载Android 11.0操作系统, 主频最高2.0GHz,效能有大幅提升;采用12nm先进工艺,具有低功耗高性能的特点。 该工业平板主板搭载了IMG GE8320图形处理器,最高主频为680MHz, 支持108…

Flume 之自定义Sink

1、简介 前文我们介绍了 Flume 如何自定义 Source, 并进行案例演示,本文将接着前文,自定义Sink,在这篇文章中,将使用自定义 Source 和 自定义的 Sink 实现数据传输,让大家快速掌握Flume这门技术。 2、自定…

Python - 深夜数据结构与算法之 Sort

目录 一.引言 二.排序简介 1.排序类型 2.时间复杂度 3.初级排序 4.高级排序 A.快速排序 B.归并排序 C.堆排序 5.特殊排序 三.经典算法实战 1.Quick-Sort 2.Merge-Sort 3.Heap-Sort 4.Relative-Sort-Array [1122] 5.Valid-anagram [242] 6.Merge-Intervals […

Java NIO (二)NIO Buffer类的重要方法(备份)

1 allocate()方法 在使用Buffer实例前,我们需要先获取Buffer子类的实例对象,并且分配内存空间。需要获取一个Buffer实例对象时,并不是使用子类的构造器来创建,而是调用子类的allocate()方法。 public class AllocateTest {static…

如何快速看懂一篇英文AI论文?

已经2024年了,该出现一个写论文解读AI Agent了。 大家肯定也在经常刷论文吧。 但真正尝试过用GPT去刷论文、写论文解读的小伙伴,一定深有体验——费劲。其他agents也没有能搞定的,今天我发现了一个超级厉害的写论文解读的agent &#xff0c…

某银行主机安全运营体系建设实践

随着商业银行业务的发展,主机规模持续增长,给安全团队运营工作带来极大挑战,传统的运营手段已经无法适应业务规模的快速发展,主要体现在主机资产数量多、类型复杂,安全团队难以对全量资产进行及时有效的梳理、管理&…

HCIA—— 16每日一讲:HTTP和HTTPS、无状态和cookie、持久连接和管线化、(初稿丢了,这是新稿,请宽恕我)

学习目标: HTTP和HTTPS、无状态和cookie、持久连接和管线化、HTTP的报文、URI和URL(初稿丢了,这是新稿,请宽恕我😶‍🌫️) 学习内容: HTTP无状态和cookieHTTPS持久连接和管线化 目…

vue2 pdfjs-2.8.335-dist pdf文件在线预览功能

1、首先先将 pdfjs-2.8.335-dist 文件夹从网上搜索下载,复制到public文件夹下. 2、在components下新建组件PdfViewer.vue文件 3、在el-upload 中调用 pdf-viewer 组件 4、在el-upload 中的 on-preview方法中加上对应的src路径 internalPreview(file) { //判断需要…

编译原理1.3习题 程序设计语言的发展历程

图源:文心一言 编译原理习题整理~🥝🥝 作为初学者的我,这些习题主要用于自我巩固。由于是自学,答案难免有误,非常欢迎各位小伙伴指正与讨论!👏💡 第1版:自…

IPv6隧道--GRE隧道

GRE隧道 通用路由封装协议GRE(Generic Routing Encapsulation)可以对某些网络层协议(如IPX、ATM、IPv6、AppleTalk等)的数据报文进行封装,使这些被封装的数据报文能够在另一个网络层协议(如IPv4)中传输。 GRE提供了将一种协议的报文封装在另一种协议报文中的机制,是一…

个人网站制作 Part 7 添加用户认证和数据库集成 | Web开发项目

文章目录 👩‍💻 基础Web开发练手项目系列:个人网站制作🚀 用户认证与数据库集成🔨添加用户认证🔧步骤 1: 使用Passport.js 🔨集成数据库🔧步骤 2: 使用MongoDB和Mongoose &#x1f…