基于活文档理念的 UI 自动化测试框架

1,414 阅读9分钟

酷家乐作为一家 SAAS 软件公司,高效且稳定的向客户提供产品服务是公司的基石。其中 UI 自动化测试是持续交付过程中的重要一环,目前酷家乐内部已投入使用了的一套自研的 UI 自动化测试框架--KoolTest。KoolTest 是我们参考了业界 BDD、DDD、活文档的一些思想,并结合我们自己的实践而成,希望下面这篇文章能给大家在如何持续交付稳定软件方面带来一些新的想法。现 KoolTest 的 java 运行时部分已开源,欢迎大家共建。

前言

不管是需求文档、代码、还是测试用例,本质上都是一种知识的表达,不同时期每一种知识载体的权威性也不同。比如在需求评审阶段,需求文档就是最权威的知识。而测试阶段,测试用例又变成了最权威的知识载体。当代码上线后,则代码就成了最权威的知识载体(无线上 Bug情况下)。如果每一步都做的很完美,那么这三个载体承载的知识点是一样的,只是表达的方式不同,但他们都具有权威性。但在现实中,我们是不可能做到如此完美的。很多公司的情况是,随着不断的迭代,需求文档和测试用例因为更新不及时,基本没有太大参考价值,线上代码成为了唯一的权威知识载体。随着研发人员的变动和项目的变大,最终导致没有任何一个人可以全盘知道这个系统的所有细节,代码上线也变得心惊胆战,而这导致了无法稳定的可持续性交付。

那我们要如何去解决这类问题呢?很自然想到的一个是,我们可以建一个文档,这个文档要尽可能和线上的权威知识源保持一致,在上线前投入人力按照文档上的说明逐条执行,如果出现问题就和开发沟通,去识别到底是产品需求的变化还是产生了 BUG。另一个可能想到的是我们要上自动化,我们再写一堆测试代码去描述业务,然后自动化执行发现业务问题。 这两种方法本质都是想在除了代码作为唯一权威知识源的情况下,去构建另外一个冗余的权威知识源。而这两种各自都有其缺点,第一种需要大量的人力资源投入做到文档的更新和执行上,往往很容易漏测。第二点产生的冗余知识代码书写和维护本身也是一个大难题,最终也只局限在少部分人中,最终一样不断人来人走,变得无人能懂。

那我们思考,可否把这些冗余的知识源既可以用领域语言的表达出来,而且还可以执行呢?目前行业中 DDD 和 BDD 的思想给我们提供了一些思路,下面说下我们是如何做的:

词汇表

“控制了词汇的人就是控制了思想”。

DDD (Domain Driven Development) 由 Eric Evans 在 2003年写的 《领域驱动设计:软件核心复杂性应对之道》提出,它主要提倡将重点放在特定的业务领域上,即编写可以直接表达领域知识的代码,而不在领域分析和可执行代码之间进行转换。

BDD(Behavior Driven Development)的本质是为了有效地分享知识,它解释了应用程序正在做什么,它可以促进不同参与者之间的深入对话,及早发现误解和歧义。

软件终归是为了解决现实中的问题,而现实中的每一类问题其实都有这个领域的专家,所以 DDD 提倡我们开发软件时要与领域专家进行频繁且密切的对话,然后直接使用这个业务领域的知识进行建模和编程。而我们公司有着大量的业务线,涉及到许许多多的领域,每一个领域都有其特定的知识,使用 DDD 的思想可以比较好的帮助我们去学习构建更好服务于特定领域的架构体系。如果把普通的网页操作、手机操作本身当作一个领域,我们基于 WebDriver 协议构建了针对 Web 和 App 侧基本操作行为的词汇表,该词汇表中的词汇已开源,基础词汇,并且配合 VSCode 插件大大简化了书写的复杂度。

当然如果只有我们基本操作类型是远远不够的,我们框架需要允许用户自定义自己的领域单词表。在我们的框架中,我们定义了一个特别的词汇叫做 macro(宏),宏你可以理解成一个原子性的操作表达,比如 调整fov、模型移动 等等,宏本身可以使用基本词汇去实现,基本词汇中有一个很强大的词汇叫做 execute_script,这个词汇支持在浏览器中运行 js 代码,所以理论上我们可以使用宏实现大多数前端操作的领域词汇。框架结构图如下:

这里举一个如何定义一个领域词汇叫做 "调整fov" 例子,我们可以如下实现:

首先增加一个文件 调整fov.macro,该宏可以如此实现

* execute_script ${1}
"""
// 这里有该词汇的 js 脚本实现,脚本运行在浏览器中
"""

然后在任何需要用到这个词汇的地方,我们可以如下方式使用

* macro 调整fov -10 // 当前场景 fov 减 10

此处的 调整fov 只是一个例子,最终领域词汇是通过领域知识提炼来的,而领域知识是需要我们与领域专家沟通对话来的。我们提供了这样的灵活性后,如何去定义这些领域词汇,就是我们下一步需要深入去思考实现的。

书写

有了词汇,我们还得思考如何更方便的书写,在我们综合考虑了 Chrome 插件方案、自建 IDE 方案、VSCode 插件方案后,最终从成本和可用性方便综合考虑选择了 VSCode 插件方案。

在 VSCode 中安装上 KoolTest 插件后,可以营造出一个书写的 IDE 的环境:

1,书写提示,每个单词可以智能提示,并且可以直接在 Vscode 中查到相关的使用说明。

2,脚本回放,直接在脚本的对应位置即可点击快速运行脚本。

3,方便的调试、录制、截图功能

我们可以在场景的任意位置加入 debugger 关键字,再点击脚本回放时,框架会运行到 debugger 处,此时浏览器会停留在这一页面,方便去获取当前页面的元素用来写脚本。我们也可以使用提供的辅助工具加快书写,比如自动录制脚本和生成截图功能。自动录制会帮你把当前的鼠标操作录制下来,并且在停止录制时自动生成相关的脚本,我们这边定义自动录制只局限在 macro 中,作为一个原子操作,因为我们最终的目的是要让用例可读。

活文档

当我们把知识书写出来后,如何让这个知识产生更大的效力?我们可以参考目前业界提出的“活文档”的思想,“活文档” 一词最先在 Gojko Adzic 的《实例化需求:团队如何交付正确的软件》中提出,它是指这些文档不但能被非技术人员看懂,而且还能自动化执行,并且时刻保持最新,非常可靠。所以我们的用例不只是脚本,也是文档,可以按照文档书写的方式来思考组织形式:

1,文件夹的组织方式

可以把知识按照领域划分成不同的文件夹

2,命名约定

可以按照一定约定命名每一个场景,这样在运行这类知识时只需要加上 --name ^回款列表 就可以运行所有回款列表相关的知识

3,打标签

比如有些知识所处在核心链路,所以他们更重要,回归的次数和重视程度也会更高,那我们可以定义一个 @CoreContext 的标签,标签是一个很好的文档组织方式,所以我们会定义一系列的标签去定义一个测试场景,并且方便我们后续对知识进行快速查找和管理。

如上当我们想只运行核心链路的知识时可以用以下命令:

--tags "@CoreContext,~@WIP" // 运行所有 @CoreContext 的场景,而不要运行 @WIP 的场景

4,知识的描述和意图

意图一般以用户故事的方式呈现,格式为:作为xxx,我想要xxx,要知道我们这些用例本身就具有很强大的分享沟通能力,所以写上用户故事,让非技术人员也能来参考。如果你遵守比较好的书写规范,那么这些知识本身就能成为很好的文档,任何参与人员基本都能读懂,并且这些知识还是可以运行的。如下截图是我们内部平台的展示页面:

写在最后

以上三个章节阐述了我们对 UI 自动化测试的理解。目前 KoolTest 除了 Web 侧,也同时支持 Android、iOS、PC,框架大致如下。

有了运行时,我们基于 jenkins 搭建了一个云端的测试集群,并通过封装 jenkins 提供的 RestAPI,结合公司内部的 gitlab、ldap、企业微信在其上通过 Next.js + Postgresql 搭建了一个测试平台,可以让测试人员自由的在平台创建测试计划,设定执行时间,并且会通过企业微信及时获得测试结果。目前 KoolTest 的核心 Java 运行时部分已开源,欢迎大家一起共建:github.com/kujiale-Mob…。也欢迎在 VSCode 中搜索 kool-test-script 插件,快速体验。后续的文章,我们会详细说明 KoolTest 是如何做到多类型终端支持和一个 UI 自动化测试平台是如何搭建运行的,欢迎继续关注。

酷家乐作为一家 SAAS 软件公司,高效且稳定的向客户提供产品服务是公司的基石。其中 UI 自动化测试是持续交付过程中的重要一环,目前酷家乐内部已投入使用了的一套自研的 UI 自动化测试框架--KoolTest。KoolTest 是我们参考了业界 BDD、DDD、活文档的一些思想,并结合我们自己的实践而成,希望下面这篇文章能给大家在如何持续交付稳定软件方面带来一些新的想法。现 KoolTest 的 java 运行时部分已开源,欢迎大家共建。

前言

不管是需求文档、代码、还是测试用例,本质上都是一种知识的表达,不同时期每一种知识载体的权威性也不同。比如在需求评审阶段,需求文档就是最权威的知识。而测试阶段,测试用例又变成了最权威的知识载体。当代码上线后,则代码就成了最权威的知识载体(无线上 Bug情况下)。如果每一步都做的很完美,那么这三个载体承载的知识点是一样的,只是表达的方式不同,但他们都具有权威性。但在现实中,我们是不可能做到如此完美的。很多公司的情况是,随着不断的迭代,需求文档和测试用例因为更新不及时,基本没有太大参考价值,线上代码成为了唯一的权威知识载体。随着研发人员的变动和项目的变大,最终导致没有任何一个人可以全盘知道这个系统的所有细节,代码上线也变得心惊胆战,而这导致了无法稳定的可持续性交付。

那我们要如何去解决这类问题呢?很自然想到的一个是,我们可以建一个文档,这个文档要尽可能和线上的权威知识源保持一致,在上线前投入人力按照文档上的说明逐条执行,如果出现问题就和开发沟通,去识别到底是产品需求的变化还是产生了 BUG。另一个可能想到的是我们要上自动化,我们再写一堆测试代码去描述业务,然后自动化执行发现业务问题。 这两种方法本质都是想在除了代码作为唯一权威知识源的情况下,去构建另外一个冗余的权威知识源。而这两种各自都有其缺点,第一种需要大量的人力资源投入做到文档的更新和执行上,往往很容易漏测。第二点产生的冗余知识代码书写和维护本身也是一个大难题,最终也只局限在少部分人中,最终一样不断人来人走,变得无人能懂。

那我们思考,可否把这些冗余的知识源既可以用领域语言的表达出来,而且还可以执行呢?目前行业中 DDD 和 BDD 的思想给我们提供了一些思路,下面说下我们是如何做的:

词汇表

“控制了词汇的人就是控制了思想”。

DDD (Domain Driven Development) 由 Eric Evans 在 2003年写的 《领域驱动设计:软件核心复杂性应对之道》提出,它主要提倡将重点放在特定的业务领域上,即编写可以直接表达领域知识的代码,而不在领域分析和可执行代码之间进行转换。

BDD(Behavior Driven Development)的本质是为了有效地分享知识,它解释了应用程序正在做什么,它可以促进不同参与者之间的深入对话,及早发现误解和歧义。

软件终归是为了解决现实中的问题,而现实中的每一类问题其实都有这个领域的专家,所以 DDD 提倡我们开发软件时要与领域专家进行频繁且密切的对话,然后直接使用这个业务领域的知识进行建模和编程。而我们公司有着大量的业务线,涉及到许许多多的领域,每一个领域都有其特定的知识,使用 DDD 的思想可以比较好的帮助我们去学习构建更好服务于特定领域的架构体系。如果把普通的网页操作、手机操作本身当作一个领域,我们基于 WebDriver 协议构建了针对 Web 和 App 侧基本操作行为的词汇表,该词汇表中的词汇已开源,基础词汇,并且配合 VSCode 插件大大简化了书写的复杂度。

当然如果只有我们基本操作类型是远远不够的,我们框架需要允许用户自定义自己的领域单词表。在我们的框架中,我们定义了一个特别的词汇叫做 macro(宏),宏你可以理解成一个原子性的操作表达,比如 调整fov、模型移动 等等,宏本身可以使用基本词汇去实现,基本词汇中有一个很强大的词汇叫做 execute_script,这个词汇支持在浏览器中运行 js 代码,所以理论上我们可以使用宏实现大多数前端操作的领域词汇。框架结构图如下:

这里举一个如何定义一个领域词汇叫做 "调整fov" 例子,我们可以如下实现:

首先增加一个文件 调整fov.macro,该宏可以如此实现

* execute_script ${1}
"""
// 这里有该词汇的 js 脚本实现,脚本运行在浏览器中
"""

然后在任何需要用到这个词汇的地方,我们可以如下方式使用

* macro 调整fov -10 // 当前场景 fov 减 10

此处的 调整fov 只是一个例子,最终领域词汇是通过领域知识提炼来的,而领域知识是需要我们与领域专家沟通对话来的。我们提供了这样的灵活性后,如何去定义这些领域词汇,就是我们下一步需要深入去思考实现的。

书写

有了词汇,我们还得思考如何更方便的书写,在我们综合考虑了 Chrome 插件方案、自建 IDE 方案、VSCode 插件方案后,最终从成本和可用性方便综合考虑选择了 VSCode 插件方案。

在 VSCode 中安装上 KoolTest 插件后,可以营造出一个书写的 IDE 的环境:

1,书写提示,每个单词可以智能提示,并且可以直接在 Vscode 中查到相关的使用说明。

2,脚本回放,直接在脚本的对应位置即可点击快速运行脚本。

3,方便的调试、录制、截图功能

我们可以在场景的任意位置加入 debugger 关键字,再点击脚本回放时,框架会运行到 debugger 处,此时浏览器会停留在这一页面,方便去获取当前页面的元素用来写脚本。我们也可以使用提供的辅助工具加快书写,比如自动录制脚本和生成截图功能。自动录制会帮你把当前的鼠标操作录制下来,并且在停止录制时自动生成相关的脚本,我们这边定义自动录制只局限在 macro 中,作为一个原子操作,因为我们最终的目的是要让用例可读。

活文档

当我们把知识书写出来后,如何让这个知识产生更大的效力?我们可以参考目前业界提出的“活文档”的思想,“活文档” 一词最先在 Gojko Adzic 的《实例化需求:团队如何交付正确的软件》中提出,它是指这些文档不但能被非技术人员看懂,而且还能自动化执行,并且时刻保持最新,非常可靠。所以我们的用例不只是脚本,也是文档,可以按照文档书写的方式来思考组织形式:

1,文件夹的组织方式

可以把知识按照领域划分成不同的文件夹

2,命名约定

可以按照一定约定命名每一个场景,这样在运行这类知识时只需要加上 --name ^回款列表 就可以运行所有回款列表相关的知识

3,打标签

比如有些知识所处在核心链路,所以他们更重要,回归的次数和重视程度也会更高,那我们可以定义一个 @CoreContext 的标签,标签是一个很好的文档组织方式,所以我们会定义一系列的标签去定义一个测试场景,并且方便我们后续对知识进行快速查找和管理。

如上当我们想只运行核心链路的知识时可以用以下命令:

--tags "@CoreContext,~@WIP" // 运行所有 @CoreContext 的场景,而不要运行 @WIP 的场景

4,知识的描述和意图

意图一般以用户故事的方式呈现,格式为:作为xxx,我想要xxx,要知道我们这些用例本身就具有很强大的分享沟通能力,所以写上用户故事,让非技术人员也能来参考。如果你遵守比较好的书写规范,那么这些知识本身就能成为很好的文档,任何参与人员基本都能读懂,并且这些知识还是可以运行的。如下截图是我们内部平台的展示页面:

写在最后

以上三个章节阐述了我们对 UI 自动化测试的理解。目前 KoolTest 除了 Web 侧,也同时支持 Android、iOS、PC,框架大致如下。

有了运行时,我们基于 jenkins 搭建了一个云端的测试集群,并通过封装 jenkins 提供的 RestAPI,结合公司内部的 gitlab、ldap、企业微信在其上通过 Next.js + Postgresql 搭建了一个测试平台,可以让测试人员自由的在平台创建测试计划,设定执行时间,并且会通过企业微信及时获得测试结果。目前 KoolTest 的核心 Java 运行时部分已开源,欢迎大家一起共建:github.com/kujiale-Mob…。也欢迎在 VSCode 中搜索 kool-test-script 插件,快速体验。后续的文章,我们会详细说明 KoolTest 是如何做到多类型终端支持和一个 UI 自动化测试平台是如何搭建运行的,欢迎继续关注。