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
使用 Cypress 的最佳实践
为了提高测试结果的确定性,有如下一些指导思想和最佳实践:
代码操作胜过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>
这个按钮的众多属性中, id
、 class
、 name
、 提交
都可能会改变,尤其是 class
,相当不稳定,会引起定位不到这个按钮,引发碎片化。而额外添加的属性 data-cy
则可以完全受控,是定位按钮的上策。
定位代码如下:
cy.get('[data-cy=submit]').click()
将测试行为限定在页面之内
与之相对的是超出页面之外的测试,指:新开tab页面、新开浏览器窗口等。因为 Puppeteer 能完全操控浏览器,所以用 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-NC 授权。
欢迎转载,请保留原文链接 https://www.lfhacks.com/tech/cypress-best-practise/ ,且不得用于商业用途。