Cypress 的指导思想和最佳实践

发表时间

Cypress 作为全新的 UI 测试框架,不仅提供了便于快速开发的API 和实时观看界面,比 TestCafe 和 Puppeteer 区别的是,它提供了一套与二者不同的方法论,本文试着做一总结。

目录

本文部分内容来自 这篇文章

预置文件目录

安装 Cypress 后, 安装程序将自动生成4个文件夹作为脚手架,分别是:

 cypress
 ├─ fixture
 ├─ integration
 ├─ plugins
 └─ support

fixture 文件夹放置预制的常量,用于定义测试程序与被测程序的状态。关于 fixture 的概念理解,可以参考这篇文章:《前端测试工具 TestCafe 测试代码结构》

integration 文件夹放置测试规范(spec),也就是测试脚本。因为界面上的测试属于集成测试(integration test)一层,所以得名。

plugins 放置外部提供的程序

support 放置抽象和复用的函数,以简化测试脚本的编写。

这种明晰的目录结构提供了一种方法论:代码逻辑与数据分离,与可复用的代码块分离。

不必写 Page Object

一提到 UI 自动化测试,似乎 Page Object 是绕不过的开发模式。这种模式究竟是提高或者降低效率,不能一概而论,就像开车未必时时处处都比走路快一样。效果如何仍然取决于具体的项目和使用的框架。

目前看来,如果框架提供的 API 位于底层(比如 Selenium Puppeteer ),换句话说就是不提供页面行为级别的API,使用 Page Object 会提高开发和维护效率。

而提供了页面行为级别 API 的框架,(例如 TestCafe Cypress ),Page Object 似乎就有些多余,因为本来这种设计模式就是为了提供页面的行为描述,如果框架提供的 API 级别足够高,那么 Page Object 完全没有必要。

另外,上一节 中还提到了能讲重复动作抽象到 support 文件夹里,更提现了对 Page Object 的替代。

追求确定性,避免碎片化结果

UI 自动化最大的诟病和困扰,当属两条:维护困难和运行不稳定,这和Selenium有很深的渊源。

在 Selenium 时代,使用同一套测试代码,前后两次的测试结果有可能不一样,而且如果测试场景数量较多,很难有一次能全部通过。这种不可重复性,让测试活动几乎毫无价值,令人苦恼。

这种无法预料结果的测试称为 Flaky Tests,即碎片化的、易碎的测试。为了减轻碎片化现象,除了不断优化测试框架内部机制外,还需要使用者在设计测试过程时,抛弃一些旧习惯,有针对性的排除不确定性,在测试中的每一步追求确定性。

碎片化测试产生的原因大致有如下:

  • 影响结果的因素众多,但是测试脚本只掌控了其中一部分,没有全面掌握全部因素,导致测试结果失控。比如影响UI测试的因素就比普通接口测试的因素多很多,所以普通接口测试相对UI测试稳定很多,但是当接口庞大起来,各参数之间耦合度上升后,接口测试也会变得不稳定起来。
  • 等待机制不够合理。Selenium 的阻塞等待是造成失败的主要原因,这一点在Puppeteer 的异步等待得到了改善,在TestCafe 中更是加入了 反复的探测机制 ,如下图:

Cypress 也有类似的多次尝试机制,叫 Retry-ability

为了提高测试结果的确定性,有如下一些指导思想和最佳实践:

代码操作胜过UI操作

比如要到达一个特定页面,用模拟鼠标点击链接,就不如直接访问 URL的方法好

// UI 操作,运行不稳定
cy.contains('跳转页面').click()

// 确定性较好
cy.visit('some/path')

因为UI 操作牵涉到定位元素,引入了不确定性,所以方法不推荐

通过增加额外的 attribute 定位元素

样式、ID、包含文字等定位元素方法,虽然方便,但是随着产品的迭代,非常容易改变,造成测试脚本的过时和测试运行的失败。比如下面这些:

cy.get('#submitbutton').click()
cy.get('div.submit').click()
cy.contains('提交').click()

对策是引入额外的、稳定的、完全受控的属性。比如下面这个按钮:

<button id="main" class="btn btn-large" name="submission" role="button" data-cy="submit">提交</button>

这个按钮的众多属性中, idclassname提交 都可能会改变,尤其是 class ,相当不稳定,会引起定位不到这个按钮,引发碎片化。而额外添加的属性 data-cy 则可以完全受控,是定位按钮的上策。

定位代码如下:

cy.get('[data-cy=submit]').click()

将测试行为限定在页面之内

与之相对的是超出页面之外的测试,指:新开tab页面、新开浏览器窗口等。因为 Puppeteer 能完全操控浏览器,所以可以访问到新开 tab 。 但是Cypress 不支持 多Tab 的操作,因为作者认为并无必要。如果要测试新开的tab,完全可以直接访问该页面,而不需要通过本页面触发打开,测试打开 tab 这一过程是无意义的。

如果实在想这么操作,比如有一个类似这样的链接: <a href="/foo" target="_blank"> 想要检查新开页面的内容,可以用如下的例子完成:

cy.get('a')
  .should('have.attr', 'href')
  .then(function(href){
    cy.visit(href)
    cy.title().should('include','新页面')
})

上面这段代码从 <a> 元素中提取了 href 属性,然后直接用 visit() 访问页面

减少条件判断

降低测试用例间的耦合度

理想情况下,各测试用例间应该是完全独立,不依赖任何其他用例,就可以成功运行。

有些断言并无必要

有下面两条理由,让我们写测试 spec时,不需要为每一步都加上断言:

自带默认断言

Cypress 很多 API 内置了默认断言(default assertions),例如:

  • cy.visit() 要求页面返回 text/html ,并且状态码是200
  • cy.request() 要求服务端真实存在并发回响应
  • cy.get()cy.contains()cy.find() 等定位元素的函数要求元素真实存在,或者稍等片刻存在(存在重试机制)
链式动作

如果一连串动作里,如果下游动作能正确执行,本身就是对上游动作的断言,(比如点击按钮首先就要能找到这个按钮)

cy.get('button')
  .should('exist')   // 多余
  .click()

相关文章
除非特别说明,本站文章均系原创,并采用 署名协议 CC-BY 授权。
欢迎转载,惟请保留原文链接:https://lfhacks.com/tech/cypress-best-practise