通过视觉测试保持端到端质量
已发表: 2022-03-10测试是任何开发人员工作流程的关键部分。 它帮助我们确保我们的项目将保持高水平的质量,并防止任何讨厌的错误进入野外。
但通常自动化测试管理起来很麻烦。 在无穷无尽的代码以确保您提供全面覆盖和处理前端测试的脆弱性之间——一个简单的选择器更改可能会完全破坏端到端的工作流程——有时感觉就像一个上坡路战斗。
通过添加自动化视觉测试,我们可以消除那些不稳定的测试,通过利用我们网站或应用程序的屏幕截图的智能图像比较来提升我们的测试管道以提供覆盖(以及更多)。
在我们深入了解可视化测试之前,让我们花一点时间来回顾一下不同类型的自动化测试以及它们如何组合在一起。 然后,我们将讨论究竟什么是可视化测试,以及它如何为您的项目提供另一个级别的测试覆盖率。
快速了解一些类型的自动化测试
自动化测试是开发周期中一个有趣的部分。 一些客户或利益相关者能够清楚地看到他们提供的价值,但其他人更愿意将任何开发时间花在纯粹的功能开发上。
这有时可能是违反直觉的,自动化测试的目标是保护业务或防止团队不得不首先花时间修复错误。 编写可靠的自动化测试可以防止重大经济损失! 最终,有些人比其他人更愿意承担风险。
幸运的是,这种价值并不总是难以推销,当我们有时间专注于质量自动化测试时,我们有多种选择来处理这些测试,如单元测试、集成测试、端到端测试和视觉测试(也可以为前三个提供扩展的覆盖范围)。
当应用每种测试的优势时,我们能够花更多的时间编写测试,这些测试实际上可以帮助保护我们的工作并避免客户的挫败感。
让我们看一下这些测试策略中的一些在实践中的样子。
单元测试
单元测试侧重于测试应用程序的较小的、集中的区域。 想要测试促销后计算订单总额的功能是否正常工作? 你想写一个单元测试。
function myBusinessLogic(pricePerItem, quantity, discount) { const subtotal = pricePerItem * quantity; return subtotal - ( discount * subtotal ); } expect(myBusinessLogic(2, 4, .1)).toEqual(7.2);
单元测试最重要的部分是它们编写起来很便宜,并且不需要很多时间来运行。 这就是为什么您经常会看到公司花费大量时间构建一套单元测试来捕获应用程序的这些细粒度部分。
但是由于集中测试,单元测试可能无法涵盖这些不同部分如何协同工作,这就是我们开始进入集成测试的地方。
集成测试
集成测试的目标是获取应用程序的较小部分和组件并测试它们如何协同工作。 一个常见的例子可能是 UI 的特定部分如何响应交互,然后是对服务器或数据库的请求。
cy.get('.add-to-cart').click(); cy.url().should('contain', 'cart'); cy.get('.cart li').contains('My Item');
您的小型 UI 组件完全有可能按预期工作。 您的合成事件可能会在 onClick 实例上正确触发。 包装您的 API 请求的代码可能会与一些模拟数据完美地执行。 但是这两个部分之间可能存在单元测试可能无法捕捉到的漏洞。
集成测试是测试应用程序的一种令人信服的方法,但是当您希望测试“所有东西”时,您可以更进一步。
端到端测试
端到端测试捕获整个用户的端到端旅程,以实现集中的工作流程。 例如,如果我正在建立一个电子商务商店,那么“愉快的路径”(或阻力最小的预期路径)将是找到一种产品,将其添加到购物车中,然后为这些商品付款。 如果我正在编写端到端测试,我会记录整个过程,即从在产品列表页面上找到产品到为该项目付款。
cy.visit('/products'); cy.get('.product a[href="/product/1234"]').click() cy.url().should('contain', 'product/1234'); ... cy.get('.order-button').click(); cy.url().should('contain', 'receipt'); cy.get('.receipt li').contains('My Item');
端到端测试的重要部分在于它本质上是一个大型集成测试。 您正在捕获应用程序的许多不同组件,包括 UI 的工作方式、API 是否正确响应以及这些部分是否协同工作。
问题是端到端测试,甚至集成测试,需要更多的时间来编写,而且它们也需要更长的时间来运行。 那么,我们如何才能利用我们所有的测试选项,并将一套测试组合在一起,从而提供一种有效的方式来覆盖我们的应用程序呢?
利用不同类型的测试
有多种心态通常描述了您应该花时间编写的每种类型的测试数量。
Mike Cohn 在他的《敏捷的成功》一书中提出了“测试金字塔”的概念。
他认为你应该编写更多的单元测试,它们的编写成本更低,运行速度更快。 虽然他的原始图表对各种测试的标记略有不同,但随着您更倾向于集成类型的测试,它们的运行速度会变得更慢,编写成本也会更高。 虽然这些测试很有价值,但您不希望拥有与单元测试一样多的集成或端到端测试。
拥有这种平衡可以帮助您专注于捕获应用程序的关键部分,例如带有单元测试的业务逻辑以及它们如何与集成测试一起工作,但 Kent C. Dodds 认为测试技术已经赶上了没有编写集成测试需要更大的成本权衡,这就是他的“测试奖杯”概念的用武之地。
在现代开发环境中,我们可以使用许多令人惊叹的工具,例如 Cypress、Selenium 和 Playwright,它们都使开发人员和 QA 工程师能够编写与 Chrome 和 Firefox 等浏览器轻松交互的测试。
使用 Cypress,编写一个单击按钮的测试看起来很简单:
cy.get('#my-button').click()
这可以说就像测试按钮是否与合成事件一起工作一样简单,如果不是更简单的话。 最好的部分是您正在测试该按钮在浏览器中的实际工作方式。
无论您订阅哪个图表,最终目标都是权衡成本和速度之间的不同选项,以确定适合您的特定应用程序。 重要的是不仅要在您的报道报告中达到 100%,而且要真正确保您为访问者提供的体验能够正常工作。
但是,无论您运行的测试组合如何,这些仅与 DOM(文档对象模型)交互和测试的程序化测试都缺少一大块难题:访问者如何直观地看到该应用程序。
传统类型的测试没有捕捉到什么
当您在应用程序上运行单元、集成和端到端测试时,它们都有一个共同点。 他们都在测试代码。
我的意思是他们没有测试您的应用程序的访问者实际看到的内容。
如果您正在运行一套集成测试,并且像我们之前的示例一样,测试某人可以将产品添加到购物车并购买它,那么每一步,您都会通过代码在 DOM 中找到一个元素并确认它以同样的方式工作。
这不会测试页面上的文本是否清晰等内容。 是否有人添加了一个 CSS 更改,意外地将所有东西浮动到左侧并将它们颠倒过来?
这些类型的错误被称为“视觉错误”,它们可能会出色地通过您的所有测试,但是当有人真正看到它时,它并不完全正确或更糟,完全无法使用。
实际上,我们不能指望通过传统测试对用户界面的每个细节提供 100% 的全面覆盖。 在无穷无尽的应用程序状态和我们一直在添加新功能的事实之间,它根本无法扩展。
这就是将我们引向这个故事的标题的原因:视觉测试。
什么是视觉测试?
可视化测试捕获应用程序的可见输出(如屏幕截图),并将其与另一个时间点的同一应用程序进行比较。
这通常通过首先捕获基线屏幕截图或先前捕获的具有预期结果的应用程序实例,然后将每个新测试与该基线进行比较来实现。
但是随着您的项目的开发,情况会发生变化。 随着时间的推移,当您批准新的视觉差异作为已接受的更改时,该基线将与您的应用程序一起更新。
视觉测试的类型
关于可视化测试的一个有趣的事情是有不同的方法来处理这个问题。
视觉测试的一种方法是使用逐像素比较,其中测试框架将标记出它在两个图像之间看到的任何差异。 虽然这样的比较提供了视觉测试的入门级,但它往往是不稳定的,并且可能导致很多误报。
您可以想象,在使用 Web 时,页面加载和浏览器更新之间的呈现方式往往会略有不同。 如果浏览器由于渲染更改、文本光标显示或“仅仅因为”而将页面渲染了 1 个像素,则您的部署可能会由于这些失败的测试而被阻止。
在处理动态内容时,您的测试也容易失败。 例如,如果您每天在Smashing Magazine网站的主页上进行逐个像素的视觉测试,您会遇到很多失败的测试,因为它们会产生越来越多的内容。
处理可视化测试的更好方法是利用 AI 等技术,每次运行测试时,测试框架都会智能地查看与基线相比捕获的屏幕截图。
它可以检测到两个捕获是不同的,甚至可以检测它是否是内容更改而不是布局更改。 如果某些内容实际上没有更改,它不会将该测试标记为失败,您甚至可以添加规则以忽略可能由于该内容而更改的应用程序的动态区域。
视觉测试的帮助
可视化测试能够以客户看到的方式捕捉应用程序的当前状态而蓬勃发展。 这使得它对于任何将有真人与之交互的应用程序都具有吸引力。
当它捕获该快照时,它提供了该应用程序的许多部分的覆盖范围,而不仅仅是您为其编写测试的单个粒度组件。 它最终捕获了该组件周围的上下文,从而扩大了覆盖范围。
这成为以低开销提供广泛覆盖的好方法。 回到测试金字塔或测试奖杯,我们实际上能够在所有其他测试之上提供全面的覆盖。
视觉测试如何工作?
要点很简单——将两张图像相互比较并寻找差异——但它比这更复杂一些。
图像比较
在实施可视化测试时,目标是为关键工作流提供覆盖,这些工作流可以捕捉到真人如何使用应用程序。 这通常包括某人可能看到的第一个屏幕,但这通常不是他们看到的唯一屏幕。
就像创建如何运行传统测试的策略一样,您要确保寻找最终在 UI 中产生真实结果的真实交互,例如将商品添加到购物车或将最喜欢的商品添加到您的愿望清单。
考虑到这一点,您需要在每一步都截取屏幕截图。 例如,如果您正在测试在线商店,您可能需要添加以下步骤:
- 页面上加载的产品列表;
- 选择单品后的产品页面;
- 将产品添加到购物车后的页面购物车 UI;
- 导航到购物车后的购物车页面;
- 进入结账流程后的支付和发货界面;
- 购买成功后的收据页面。
这将捕获某人通过您的在线商店时的所有交互的结果,您可以在其中验证它不仅从代码的角度在功能上工作,而且该人实际上可以在没有的情况下使用您的应用程序视觉错误干扰。
技术位
在规划屏幕截图时,您需要一种机制来自动执行这些任务。 可以基于一组任务与浏览器交互的东西。
这就是 Selenium、Cypress 和 Playwright 等流行的浏览器自动化框架的用武之地,这些测试框架利用浏览器 API 来运行您的命令,像人类一样查找和单击事物,然后您可以告诉可视化测试框架何时可视化捕获 UI 状态。
在 Cypress 和 Applitools 的情况下,Cypress 将浏览每个页面,然后 Applitools SDK 将提取 DOM 的快照,并将该快照发送到 Applitools 云,最终生成屏幕截图进行比较。
那时,根据视觉测试平台,您将获得一组结果,以突出显示的差异或漂亮的绿色复选标记(如果看起来不错)的形式。
与现有测试框架集成
就像上面的 Cypress 和 Applitools 集成一样,集成通常具有低摩擦。 许多可用的可视化测试平台可以直接集成到现有的测试框架中,这主要取决于它们可用的 SDK。
cy.visit('/product/1234'); cy.eyesOpen({ appName: 'Online Store', testName: 'Product Page' }); cy.eyesCheckWindow(); cy.eyesClose();
这意味着您通常不需要完全重写您的测试套件来加强您的测试并获得视觉覆盖; 您可以将这些检查点添加到您已有的测试中。
自动化测试
幸运的是,开发和测试相关任务的自动化已经迅速成熟,为我们如何自动化测试提供了很多很好的选择。
传统的 CI/CD 解决方案(如 Jenkins 或 Travis CI)允许您在集成和部署管道的其余部分一起在他们的环境中运行测试。 相对较新的自动化领域是 GitHub Actions 等工具,它们提供与传统 CI/CD 环境类似的机制,但就在您现有的 GitHub 存储库中。 这使得在尝试自动运行测试和其他代码任务时轻松获胜,您不需要全新的系统,而是使用现有工具。
name: Node.js CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 12.x - run: npm ci - run: npm test
但是不管你使用什么环境,最终你还是要服从测试框架的要求。 只要您可以访问诸如 Electron 或 Chrome 之类的无头浏览器,Cypress 就可以在您可以安装 Node.js 的任何地方无缝运行,这在当今非常普遍。 其他人可能需要一些额外的环境,但此时,您通常可以根据需要搭建该环境,创建运行测试所需的条件。
视觉测试有什么好处?
视觉测试将提供各种各样的好处,就像我们已经讨论过的一些好处一样,但它确实可以帮助所有利益相关者,包括高管、产品经理、开发人员、设计师,以及团队中的任何其他人。
例如,对于 CEO 或产品经理,您会确信您的测试覆盖率实际上是在捕捉真实世界的使用情况。 对于开发团队而言,您将获得同样的信心,即无论何时进行更改,您都会立即获得反馈,从而消除了在尝试快速行动时所涉及的恐惧因素。 但也有很多实际的好处。
需要维护的代码更少
与可视化测试平台集成时,您的大部分代码将围绕两件事展开:交互和屏幕截图。
交互本质上是在应用程序中导航,找到您想要捕获的页面或用户流。 无论您如何测试,您都可能需要以一种或另一种形式维护它。
另一方面,屏幕截图将涵盖您通常在测试中编写的所有断言。 通过将每个屏幕截图与基线进行比较,您可以自动确保项目的每个组件都按预期工作。
测试不那么脆弱
通过使用这些屏幕截图作为您的断言机制,您的测试将变得不那么脆弱和脆弱。
如果您正在针对 DOM 的特定部分编写断言,例如使用 ID 或自动生成的选择器,那么您将冒着对该组件进行任何更改的破坏性测试的风险。
有了 ID,有人可能只是不小心删除或更改了它。 也许您认为它仅用于功能目的并在返工时对其进行了更新,这最终破坏了测试(发生在我身上!)。
或者,如果您使用通用选择器,无论是否自动生成,它们往往是非常具体的,因为您正在测试应用程序的非常特定的部分。 如果您最终在代码中嵌套了一些 HTML 或稍微移动了一些东西,即使您没有改变它的视觉外观,它也可能最终破坏该测试。
测试人们实际使用的是什么
说到测试它的视觉外观,当视觉测试时,您正在测试您的访问者或客户实际看到的内容。
使用正确的语义 HTML 不会自动使您的项目可用。 一个小的 CSS 更改(如 z-index)可以完全改变可用性和外观。
通过捕获屏幕截图并通过用户流程的交互来比较应用程序的实际状态,您可以确保您的应用程序在功能上正常工作,并确保它不仅可供自动化机器人使用。
推荐阅读:在大型项目中管理 CSS Z-Index
测试你没想过要测试的东西
您还可以覆盖您甚至没有想过要测试的应用程序的不同部分。
考虑一下你的套件中的测试列表,那些是你想写或写的,因为你之前遇到了一个错误。 应用程序的其余部分呢?
该屏幕截图将捕获您的其他测试可能未包含的更多细节和上下文。
视觉测试不涵盖什么?
但是可视化测试并不是要替代整个测试套件的最终解决方案。 像其他类型的测试一样,它应该共存,填补其他类型的空白并最终提供更有意义的覆盖。
测试数据驱动的业务逻辑
当您进行可视化测试时,您可能能够捕捉到业务逻辑的某些方面,例如确保有人将商品添加到购物车时,数学检查出来了,但您的在线商店可能有更广泛的种类不仅仅是那个产品。
通过单元测试捕获复杂的业务逻辑仍然很重要,确保您捕获不同的用例,例如各种折扣如何影响总数。
全面的 API 测试
在处理 API 时,您可以将可视化测试视为集成测试。 我们能够测试在与浏览器交互时,我们的请求逻辑按预期工作。 但它没有全面了解该 API 的运作方式。
与业务逻辑类似,您的 API 仍应由一组测试支持,以确保其按预期工作,例如单元测试或健康检查。
视觉测试入门
作为我们腰带中的另一个工具,可视化测试确实可以帮助提供另一个级别的覆盖范围,帮助我们为我们的应用程序保持高水平的质量,但是我们从哪里开始呢?
融入开发生命周期
因为可视化测试有助于实现所有利益相关者的目标,所以它可以真正适用于开发生命周期的任何部分。
例如,传统上测试仅用于验证代码是否按预期工作,但我们也可以将视觉测试用于设计交接和 UX 协作。 团队中的设计师可以将他们的模型作为基线插入,并轻松使用它们来比较实际体验。
但是从代码的角度来看,可视化测试可以在自动化环境中蓬勃发展,例如对拉取请求运行检查、部署前的暂存环境以及确保部署后的生产环境看起来不错。
你甚至可以在 cron 上运行你的可视化测试,替换你的健康检查合成事件,这些合成事件通常是不稳定的,并且不能真正告诉你你的应用程序处于什么状态。
幸运的是,您使用的服务以及使用这些服务的集成点都有很多选择。
视觉测试的可用解决方案
确定要推进的解决方案取决于选择您将用于运行测试的库或服务。 就像我们之前提到的,最大的区别将是这些服务提供的视觉测试类型。
许多平台正在使用逐像素视觉测试来执行检查。 这包括像 Percy 和 Chromatic 这样的工具,它们将标记两个屏幕截图之间的变化。
然后你就有了人工智能驱动的视觉测试,Applitools 是目前唯一提供这种能力的服务。 Applitools 不是简单地逐个像素地检查图像,而是智能地比较图像,避免任何片状测试或误报,提供有意义的变化检测。
无论解决方案如何,您最终都需要将其集成到您的开发环境中,无论您是从头开始还是将其添加到现有的测试框架中。
集成视觉测试
在集成您选择的可视化测试平台时,您可以选择从头开始,或者以更简单的方式集成到您现有的测试框架中。 Applitools 之类的工具让这一切变得简单,其中支持的大量 SDK 有助于轻松融入现有工作流程。
一个很好的例子是,如果您已经设置并使用 Cypress 运行:
it('should log into the application', () => { cy.get('#username').type('colbyfayock'); cy.get('#password').type('Password1234'); cy.get('#log-in').click(); cy.get('h1').contains('Dashboard'); });
如果您已经在执行程序化测试,您可以简单地进行可视化测试以提供另一层覆盖。
it('should log into the application', () => { cy.eyesOpen({ appName: 'My App', testName: 'Login' }); cy.get('#username').type('colbyfayock'); cy.get('#password').type('Password1234'); cy.get('#log-in').click(); cy.eyesCheckWindow(); cy.eyesClose(); });
一些 SDK 让它变得更加容易,如果您已经在运行 Storybook 库,您需要做的就是使用 npm 安装包并运行一个简单的命令,然后您就可以完全覆盖所有组件。
npm install @applitools/eyes-storybook --save-dev npx eyes-storybook
最终,最大的问题是您要使用什么测试框架,以及您要使用的服务是否支持该框架。
视觉测试的创造性使用
除了为您的应用程序获得另一个级别的测试覆盖率之外,您还可以通过多种其他方式来利用可视化测试。
- 正常运行时间监控
定期运行有意义的可视化测试,而不是使用脆弱的合成事件进行典型的正常运行时间监控。 - 设计/用户体验协作
从移交到可用性问题,使用可视化测试为整个团队提供协作的媒介。 - 可访问性测试
捕获可能限制应用程序可访问性的关键问题。 - 历史快照
定期运行可视化测试可以帮助您捕获快照,从而轻松地为您提供一种参考项目旧状态的方法。 - 本地化测试
借助基于 AI 的视觉测试能够检测到内容变化,无论使用何种语言,您都可以确保一切正常并按预期工作。 奖励:在尝试比较给定语言的不同版本时,您可以减少开销。
通过将视觉元素添加到您的测试中,您将获得更多选项来添加有意义的方式来保持应用程序的高质量。