E2E测试用例生成方案探索

本文作者:来自MoonWebTeam的kanedongliu

本文编辑:kanedongliu

一、背景

E2E测试在众多的测试形式中处于金字塔顶尖,相对较少的测试用例就能够覆盖更多的场景,性价比高。但是由于元素定位,调试等原因,导致编写代码的成本较高,本文章旨在探索两种E2E测试用例生成方案,录制式与BDD测试用例生成,供学习交流

众多的测试用例生成方案都可以抽象成上述三个步骤,差异之处主要在获取测试用例步骤的方式

二、录制式测试用例生成

2.1、简述

Playwright 的代码生成是一个非常Amazing的能力,为开发人员和测试人员提供了更多的灵活性和稳健性,会实时记录操作,并生成测试用例代码,还可以选择生成的语言,打破了常规边写代码边调试,发现选择器或者流程不正确而测试用例失败的窘境 通过以下指令即可开启代码生成的能力

bash 复制代码
npx playwright codegen demo.playwright.dev/todomvc

随即会打开两个窗口,一个是浏览器窗口,另一个是代码记录窗口,通过操作浏览器实时记录动作,并支持断言,以及转换为不同语言的代码 同时也可以通过代码生成器选择定位器,然后将定位器复制粘贴到代码中,Playwright找出最佳定位器,最优先考虑使用角色、文本和测试 ID 识别元素,CSS 和 XPath 的优先级较低,如果生成器找到与定位器匹配的多个元素,它将改进定位器,使其具有唯一标识目标元素

2.2、原理

  1. 当我们运行codegen指令时,主进程会打开渲染子进程,以及记录的子进程,渲染子进程是在chromium基础上进行改造的程序,并由官方统一地维护,更新新特性,并在每次执行指令时,都会检查更新
  2. 在对浏览器进行操作时,playwright会根据优先级角色,文本和测试ID定位器,如果生成器识别出与定位器匹配的多个元素,他将改进定位器,使其具有弹性并唯一地识别目标元素,并通过进程间的通信,传递消息,记录动作,以点击动作为例,将会序列化如下数据:
json 复制代码
"o": [
  {
    "k": "name",
    "v": "click"
  },
  {
    "k": "selector",
    "v": "internal:attr=[placeholder='What needs to be done?'i]"
  },
  {
    "k": "button",
    "v": "left"
  },
  {
    "k": "clickCount",
    "v": 1
  }
]

其中selector的格式为字符串,其完整的格式为:

typescript 复制代码
internal:[selectorType]=[body]
// 例如
page.getByPlaceholder('What needs to be done?') =>
internal:attr=[placeholder='What needs to be done?'i]


page.getByRole('button', { name: 'Login' }) =>
internal:role=button[name="Login"i]


page.locator('mat-card-actions').getByRole('button', { name: 'Login' }) =>
mat-card-actions >> internal:role=button[name="Login"i]
  • internal:表示选择器的范围为内部,区别于多个iframe的情况
  • selectorType:选择器类型,包括role,attr,text,testid等
  • body:具体的内容,格式取决于selectorType
  • modifier:修饰符,取值为s|i,表示是否忽略大小写

清晰的可以看出通过鼠标左键点了placeholder为What needs to be done?文案的元素一次 上述的"o"表示对象,会反序列化成对象

  1. 上述的"o"表示对象,会反序列化成对象
json 复制代码
{
  name: "click",
  selector: "#kw",
  button: "left",
  clickCount: 1
}

该对象即是表示一个click的动作,当然还有其他勾选选择框,关闭页面等动作类型

typescript 复制代码
export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction;
动作名称 透传的额外参数
点击 selector: string button: 'left' | 'middle' | 'right' clickCount: number position?: Point
勾选 selector: string
取消勾选 selector: string
打开页面 url: string
填入 selector: string text: string
页面跳转 url: string
按键 selector: string key: string modifiers: number
...
  1. 通过不同语言类型的生成器,按照动作类型,生成不同的代码,再加上不同语言特定的头和尾即可生成完整的测试代码
  1. 5.最后将所有语言生成的源代码,通过websocket通信,交由代码记录器进行更新,即可得到完整的代码内容

三、BDD测试用例生成

3.1、什么是BDD测试

行为驱动开发是由 Daniel Terhorst-North 早在 00 年代初就率先提出的,正如他在 2006 年发表的一篇名为BDD 简介的文章中所解释的那样。它源于测试驱动开发 (TDD) 的想法,它强调开发人员、测试人员和业务利益相关者之间的合作和沟通。BDD测试的目标是通过共享和理解业务需求,以及编写可读性强的测试用例,来改善软件开发的效率和质量

3.2、为什么使用BDD测试

BDD是通过高级语言描述测试用例,有如下好处:

  1. 自动化测试支持 BDD测试通常与自动化测试框架和工具结合使用,以便更高效地执行和管理测试用例。自动化测试可以提高测试的速度和准确性,并帮助团队更快地发现和修复问题
  2. 提高可读性和可维护性 BDD测试鼓励使用自然语言编写测试用例,使其更易于理解和维护。这样,不仅开发人员和测试人员可以更好地理解测试的意图,而且业务利益相关者也可以参与审查和验证测试用例
  3. 整合开发和测试 BDD测试鼓励开发人员和测试人员在测试用例的编写和执行过程中紧密合作。这有助于提前发现和解决问题,减少开发和测试之间的摩擦,并加快软件交付的速度

3.3、BDD基本过程

  1. 理解业务需求 BDD的第一步是与业务利益相关者合作,共同理解和定义软件的业务需求
  2. 编写用户故事 基于理解的业务需求,用户故事通常使用简洁的自然语言来描述,以便业务利益相关者和团队成员都能理解
  3. 定义场景和行为 在BDD中,使用Gherkin语言编写场景和行为描述。场景描述了软件的特定情境,而行为描述了在该情境下软件应该如何行为。这些描述通常使用Given、When、Then等关键字来组织
  4. 编写可执行的测试用例 基于场景和行为的描述,然后编写可执行的测试用例。这些测试用例使用自然语言和关键字来描述预期的行为和结果,测试用例可以使用Cucumber等工具来管理和执行
  5. 实现和测试 使用自动化测试框架和工具,如Appium、Puppeteer等,来自动执行和管理测试用例

3.4、BDD测试用例生成实现

3.4.1、测试用例编写语言 --Gherkin

使用Gherkin语言作为测试用例的编写语言,结构清晰,易于理解,与编程语言无关,支持多种编程语言 使用Gherkin编写的测试用例示例如下: 里面有一些关键字

  • Feature:提供高级描述 软件功能,并对相关场景进行分组,Gherkin 文档中的第一个主关键字必须始终为 Feature
  • Given:用于描述系统的初始上下文 - 场景
  • When:用于描述事件或操作。这可以是一个人与系统交互,也可以是另一个系统触发的事件
  • Then:用于描述预期结果或结果

通过这些关键字对用例进行描述,能够清晰的了解想测试的功能 下面提供了含有Examples的例子,以一个表格的形式来表现出不同的先决条件,断言的结果不同

3.4.2、BDD工具

有了BDD的描述文件,还需要一个工具来解释BDD,目前主流的是cucumber,cucumber 适用于大多数主流编程语言,并由官方维护了js,java等主流语言的基础库,生态丰富 Cucumber 读取以纯文本形式编写的可执行规范,并验证该软件是否按照这些规范所述进行操作 比如上述的BDD描述文件,则可按照下面的方式进行解析执行

javascript 复制代码
const assert = require('assert');
const { Given, When, Then } = require('@cucumber/cucumber');

function isItFriday(today) {
  if (today === "Friday") {
    return "TGIF";
  } else {
    return "Nope";
  }
}

Given('test', function () {
});

Given('today is {string}', function (givenDay) {
  this.today = givenDay;
});

When('I ask whether it\'s Friday yet', function () {
  this.actualAnswer = isItFriday(this.today);
});

Then('I should be told {string}', function (expectedAnswer) {
  assert.strictEqual(this.actualAnswer, expectedAnswer);
});

最后执行cucumber-js指令即可执行测试用例

3.4.3、原子化BDD测试指令

如何提升BDD测试指令的通用能力,将测试指令更加的细化成每一步的操作,比如"登录账户"即可细化为输入账号,输入密码,点击登录,再细化成在账户输入框中输入xxxx,在密码框中输入yyyy,点击登录按钮,最终细化为单步操作的原子指令

typescript 复制代码
Given('跳转到[{string}]', function (url: string) {
})

When('等待[{int}]秒', function (time: string) {
})

When('点击元素[{string}]', function (locator: string) {
})
When('点击文案[{string}]', function (text: string) {
})

When('在[{string}]中输入[{string}]', function (locator: string, text: string) {
})

When('在[{string}]按下[{string}]', function (locator: string, key: string) {
})

Then('存在元素[{string}]', function (locator: string) {
})

如此以来,就仅仅只是利用cucumber解析feature文件,生成DSL后,交由解析器进行执行 DSL结构: 一个测试场景包含多个测试步骤,每个测试步骤即是一条测试指令,其中既包含动作指令,又包含断言指令

typescript 复制代码
export interface Scenario {
  name: string;
  steps: Step[]
}

export interface Step {
  /** 指令 */
  command: Command,
  /** 参数 */
  params: (string | number)[]
}


/** 指令 */
export enum Command {
  /** 跳转链接 */
  Jump = 'jump',
  /** 等待 */
  Wait = 'wait',
  /** 存在元素 */
  ExistElement = 'existElement',
  /** 存在文案 */
  ExistText = 'existText',
  /** 点击元素 */
  ClickElement = 'clickElement',
  /** 点击文案 */
  ClickText = 'clickText',
  /** 点击位置 */
  ClickPosition = 'clickPosition',
  /** 在placeholder中输入文字 */
  InputText = 'inputText',
  /** 元素的文案是 */
  ElementTextIs = 'elementTextIs',
  /** 元素的文案包含 */
  ElementTextContain = 'elementTextContain',
  /** 按下 */
  Press = 'press'
}

3.4.4、DSL解析器

有了这样的DSL之后,再将该DSL按照不同的功能进行解析,比如直接运行测试用例,或者生成测试用例代码,同时生成的测试用例代码也具备语言无关性,十分灵活

  • 测试用例执行器
  • js代码生成器
  • python代码生成器
  • ...

3.5、示例

以下面购物网站为例,测试多个账号的正确性,即可通过自然语言表示测试用例,从而进一步执行测试用例,或者生成可执行的测试用例代码

四、总结

生成代码方式 生成代码的难易程度 灵活性 用例理解能力
录制式
BDD

上面讲述了两种不同的不同的生成自动化测试用例的方案,两种方式各有特色

  • 生成代码的难易程度:录制式生成测试用例代码比BDD更加的简单,只需要按照流程进行操作即可,由于浏览器是定制的原因,可操作的空间更大,自动获取更合适的元素定位,而BDD的方式还需要预置元素定位
  • 灵活性:录制式跟运行平台挂钩,更多地是功能性测试,而很难做到兼容性测试,但BDD中间产物为DSL,在解析DSL方式中会更加的灵活
  • 用例理解能力:录制式最终的生成产物为代码,不利于信息的传递,理解成本较高,而BDD以feature作为载体,更加有利于需求的理解,转化为脑图,更加有利于逻辑分支的梳理

最后,如果客官觉得文章还不错,👏👏👏欢迎点赞、转发、收藏、关注,这是对小编的最大支持和鼓励,鼓励我们持续产出优质内容。

五、关于我们

MoonWebTeam目前成员均来自于腾讯,我们致力于分享有深度的前端技术,有价值的人生思考。

六、往期推荐

穿越时空:2023年前端技术盘点与2024年技术展望

基于 Appium2 的 H5 E2E 测试实践

一文带你了解前端渲染模式演进史

长文带你深入剖析微内核架构

一文带你入门LangChain

相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者3 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人7 小时前
前端知识补充—CSS
前端·css