摆脱手动回归测试的愿望是引入自动测试的一个很好的理由。 问题是哪个? 界面开发人员Natalya Stus和Alexei Androsov回顾了他们的团队如何进行了几次迭代,并基于Jest和Puppeteer在Auto.ru中构建了前端测试:单元测试,单个React组件的测试,集成测试。 这次体验中最有趣的是在没有Selenium Grid,Java和其他东西的浏览器中对React组件进行隔离测试。

阿列克谢:
-首先,您需要告诉我们什么是《汽车新闻》。 这是一个销售汽车的网站。 搜索,个人帐户,汽车服务,备件,评论,经销店等等。 Auto.ru是一个非常大的项目,包含很多代码。 我们将所有代码编写成一个大型的代码,因为它们混合在一起了。 相同的人会执行类似的任务,例如,针对移动设备和台式机。 原来有很多代码,monorepa对我们至关重要。 问题是如何测试呢?

我们有React和Node.js,它们执行服务器端渲染并从后端请求数据。 BEM上剩余的小物件。

娜塔利亚:
-我们开始考虑自动化。 我们各个应用程序的发布周期包括几个步骤。 首先,该功能由程序员在单独的分支中开发。 之后,在同一单独的分支中,由手动测试人员测试功能。 如果一切顺利,任务将落入候选发布版本。 如果不是,则再次返回到开发迭代,再次进行测试。 除非测试人员说此功能一切正常,否则它不会成为候选版本。
组装好候选版本后,将进行手动回归-不仅是Auto.ru,而且还有我们要推出的软件包。 例如,如果我们要滚动桌面网络,则需要手动回归桌面网络。 这些是很多手动测试用例。 这样的回归花费了一个手动测试仪大约一个工作日。
回归完成后,将发生释放。 之后,发行分支合并到主服务器中。 此时,我们只需要注入仅针对桌面Web测试的主代码即可,例如,该代码可能会破坏移动Web。 这不会立即检查,而只会在下一次手动回归时检查-移动网络。

自然,此过程中最痛苦的地方是手动回归,这花费了很长时间。 当然,所有手动测试人员都厌倦了每天做相同的事情。 因此,我们决定使一切自动化。 执行的第一个解决方案是由一个单独的团队编写的Selenium和Java自测。 这些是端到端测试e2e,它测试了整个应用程序。 他们写了大约五千个这样的测试。 我们最终得到了什么?
自然地,我们加速了回归。 自动测试的通过速度比手动测试器快得多,大约比原来快10倍。 因此,他们每天执行的例行操作已从手动测试器中删除。 通过自动测试发现的错误更易于重现。 只需重新启动该测试或查看它执行的步骤-不同于手动测试器,手动测试器会说:“我单击了某项,一切都坏了。”
提供了涂层的稳定性。 我们总是进行相同的运行测试-相反,通过手动测试,测试人员会认为我们没有碰到这个地方,而这次我不会进行检查。 我们添加了测试来比较屏幕截图,从而提高了测试UI的准确性-现在我们检查了测试者用眼睛看不到的像素差异。 全部感谢截图测试。
但是有缺点。 最大的一个-对于e2e测试,我们需要一个与产品完全一致的测试环境。 它必须始终保持最新状态并可以运行。 这就需要与出售稳定支持几乎一样的力量。 自然,我们不能总是负担得起。 因此,尽管最前端的程序包中没有问题,但我们经常遇到这样的情况,即测试环境不佳或某处发生故障,并且测试失败。
这些测试也由一个单独的团队开发,该团队拥有自己的任务,在任务跟踪器中的轮流使用,并且某些新功能会有所延迟。 它们不能在新功能发布后立即出现并立即对其进行自动测试。 由于测试成本高昂且难以编写和维护,因此我们无法涵盖所有场景,而仅涵盖最关键的场景。 同时,需要一个单独的团队,它将拥有单独的工具,单独的基础架构,以及它们自己的所有。 对于人工测试人员或开发人员而言,对下降的测试进行分析也是一项艰巨的任务。 我将展示一些示例。

我们已经进行了测试。 通过了500次测试,其中一些失败了。 我们可以在报告中看到这样的事情。 这里的测试根本没有开始,并且不清楚那里是否一切都很好。

另一个例子-测试开始了,但是由于这样的错误而崩溃了。 他在页面上找不到任何元素,但是为什么-我们不知道。 要么根本没有出现此元素,要么结果是在错误的页面上,或者更改了定位器。 您所需要去的一切都动手了。

屏幕截图测试也不总是能够为我们提供良好的准确性。 在这里,我们装入某种卡,它已经稍微移动了,我们的测试下降了。

我们试图解决许多这些问题。 我们开始在产品上运行部分测试-不影响用户数据的测试不会更改数据库中的任何内容。 也就是说,我们在prod上制作了一台单独的机器,以查看prod的环境。 我们只是安装一个新的前端程序包并在其中运行测试。 该产品至少稳定。
我们将一些测试转移到了Mokey,但是我们有许多不同的后端,不同的API,并且将它们全部锁定是非常困难的任务,尤其是对于5000项测试而言。 为此,编写了一个称为嘲笑的特殊服务,它有助于轻松地为前端制作必要的Moka,并且很容易代理它们。
我们还必须购买一堆铁,以便从中启动这些测试的Selenium设备网格更大,以使它们不会掉落,因为它们无法提升浏览器,因此运行速度会更快。 即使我们尝试解决了这些问题,我们仍然得出这样的测试不适合CI的结论,它们需要很长时间。 我们不能在每个池请求中都运行它们。 我们一生中绝对不会以后再分析这些报告,这些报告将针对每个池请求生成。

因此,对于CI,我们需要快速,稳定的测试,这些测试不会由于某些随机原因而失败。 我们希望针对池请求运行测试,而没有任何测试架,后端,数据库,而没有任何复杂的用户案例。
我们希望这些测试与代码同时编写,并且测试的结果可以立即清楚地指出出问题的文件。
阿列克谢:
-是的,我们决定尝试我们想要的一切,在相同的Jest基础架构中从头到尾整理所有内容。 我们为什么选择玩笑? 我们已经在Jest上编写了单元测试,我们喜欢它。 这是一个受支持的流行工具,那里已经有很多现成的集成:React测试渲染,Enzyme。 一切都是开箱即用的,不需要构建任何东西,一切都很简单。

而Jest亲自为我赢得了胜利,这与任何摩卡不同,如果我忘记清洁它或其他东西,很难在腿上冒出某种第三方测试的副作用。 在moka中,此操作执行一次或两次,但在Jest中则很难做到:它在不同的线程中不断启动。 可能,但是困难。 对于e2e发布的Puppeteer,我们也决定尝试一下。 那就是我们得到的。

娜塔利亚:
“我还将以单元测试为例。” 当我们仅针对某些功能编写测试时,没有任何特殊问题。 我们调用此函数,传递一些参数,将发生的事情与应该发生的事情进行比较。
如果我们谈论的是React组件,那么一切都会变得更加复杂。 我们需要以某种方式渲染它们。 有一个React测试渲染器,但是对于单元测试来说不是很方便,因为它不允许我们单独测试组件。 它将组件完全渲染到最后,以进行布局。
我想展示一下如何使用酶在具有一定MyComponent的组件示例中使用React组件编写单元测试。 他得到某种道具,他具有某种逻辑。 然后,他返回Foo组件,而Foo组件又将返回bar组件,而bar组件中已经存在的bar组件实际上就是布局。

我们可以使用像浅渲染这样的酶工具。 这就是我们孤立测试MyComponent组件所需要的。 这些测试将不依赖于foo和bar内部包含的组件。 我们将仅测试组件MyComponent的逻辑。
Jest拥有诸如Snapshot之类的东西,他们也可以在这里为我们提供帮助。 “期待将要匹配的快照”将为我们创建这样的结构,只是一个文本文件,实际上存储了我们传递给我们的期望,发生的事情以及首次运行此测试时会写入此文件。 通过进一步运行测试,将获得的结果与MyComponent.test.js.snap文件中包含的标准进行比较。
在这里,我们看到整个渲染过程,它完全返回MyComponent的render方法返回的结果,而foo是什么,通常它并不在乎。 我们可以针对两种情况编写两个测试,对于MyComponent组件的两种情况编写这样的测试。

原则上,我们可以在没有快照的情况下测试同一件事,只需检查所需的脚本即可,例如,检查将哪个prop传递给foo组件。 但是这种方法有一个缺点。 如果我们向MyComponent(我们的新测试)添加一些其他元素,则不会以任何方式显示。

因此,快照测试毕竟是可以向我们展示组件内部几乎所有更改的测试。 但是,如果我们在Snapshot上编写两个测试,然后在组件中进行相同的更改,那么我们将看到两个测试都将失败。 原则上,这些失败的测试的结果将告诉我们同样的事情,即我们在其中添加了某种“ hello”。

而且这也是多余的,因此,我认为最好对同一结构使用一个Snapshot测试。 在没有Snapshot的情况下,以不同的方式检查其余的逻辑,因为Snapshot的指示性不是很高。 当您看到Snapshot时,您只会看到已经渲染了一些东西,但是不清楚您在此处测试了哪种逻辑。 如果要使用它,则完全不适合TDD。 而且它不会像文档那样起作用。 也就是说,当您查看此组件时,会看到是的,Snapshot对应于某种东西,但其中的逻辑不是很清楚。


同样,我们将在foo组件,bar组件(例如Snapshot)上编写单元测试。

这三个部分的覆盖率达到100%。 我们相信我们已经检查了一切,我们做得很好。
但是,假设我们更改了bar组件中的某些内容,为其添加了一些新的支持,并且显然已经对bar组件进行了测试。 我们更正了测试,所有三个测试都通过了我们的测试。

但是实际上,如果我们收集了整个故事,那么任何事情都将无法进行,因为MyComponent不会遇到此类错误。 我们实际上并未将期望的道具传递给bar组件。 因此,我们正在谈论一个事实,在这种情况下,我们还需要进行集成测试,以进行检查,包括是否从组件中正确调用其子组件。

拥有了这些组件并进行了更改,您将立即看到该组件中的哪些更改已受到影响。
我们在酶方面有什么机会进行整合测试? 浅渲染本身会返回这样的结构。 它有一个潜水方法,如果在某个React组件上调用它,它将失败。 因此,在foo组件上调用它,我们得到foo组件呈现的内容,这就是bar,如果再次进行下潜,我们实际上将获得bar组件返回给我们的布局。 这只是一个集成测试。

或者,您可以使用实现完整DOM呈现的mount方法立即呈现所有内容。 但是我不建议这样做,因为这将是一个非常困难的快照。 而且,通常,您不需要完全检查整个结构。 在每种情况下,您只需要检查父组件和子组件之间的集成即可。

对于MyComponent,我们添加了集成测试,因此在第一个测试中,我仅添加了下潜,结果证明,我们不仅测试了组件本身的逻辑,还测试了它与foo组件的集成。 同样,我们添加了对foo组件的集成测试,它正确地调用了bar组件,然后检查了整个链,我们确定没有任何变化会破坏我们,实际上是MyComponent的呈现。

另一个示例,已经来自真实项目。 简要介绍一下Jest和Enzyme还能做什么。 开玩笑可以做摩奇。 如果在组件中使用某些外部功能,则可以将其锁定。 例如,在此示例中,我们调用某种api,我们自然不希望在单元测试中使用任何api,因此我们只使用一些jest.fn对象擦除了getResource函数。 其实是模拟功能。 然后我们可以检查它是否被调用,调用了多少次以及带有哪些参数。 所有这些使您可以开玩笑。

在浅层渲染中,您可以将存储传递给组件。 如果您需要商店,只需将其转移到那里即可。

您还可以在已渲染的组件中更改State和prop。

您可以在某些组件上调用Simulation方法。 它只是调用处理程序。 例如,如果您模拟点击,它将在此处调用onClick作为按钮组件。 当然,所有这些内容都可以在酶的文档中阅读,很多有用的文章。 这些只是来自真实项目的几个示例。

阿列克谢:
-我们提出了最有趣的问题。 我们可以测试Jest,我们可以编写单元测试,检查组件,检查哪些元素对单击的响应不正确。 我们能够检查他们的html。 现在我们需要检查组件css的布局。

最好这样做,以使测试原理与我之前介绍的方法没有任何不同。 如果我检查html,那么我将其称为浅层渲染,然后将其呈现给我。 我想检查css,只调用某种渲染,然后只检查-不提高任何东西,也没有设置任何工具。

我开始寻找它,几乎到处都对称为Puppeteer或Selenium网格的整个问题给出了相同的答案。 打开一些选项卡,转到某个页面html,截屏并将其与上一个选项进行比较。 如果它没有改变,那么一切都很好。
问题是,如果我只想单独检查一个组件,那么什么是HTML页面? 期望-在不同条件下。

我不想为每个组件,每个状态写一堆这些页面html。 Avito表现出色。 罗马·德沃诺夫(Roma Dvornov)在哈布雷(Habré)上发表了一篇文章,顺便说一句,他发表了讲话。 他们做了什么? 他们采用组件,通过标准渲染器组装html。 然后,借助插件和各种技巧,他们收集了他们拥有的所有资产-图片,CSS。 将其全部插入html,他们将获得正确的html。

然后,他们提出了一个特殊的服务器,在那里发送html,它渲染它,并返回一些结果。 阅读一篇非常有趣的文章,但是,您可以从那里得出很多有趣的想法。

我不喜欢那里。 组装组件与将其投入生产的方式不同。 例如,我们有一个webpack,它将被某种babel资产收集,在那里被不同地拉出。 我不能保证我已经测试过现在要下载的内容。
再次,提供了一个单独的截图服务。 我想以某种方式更轻松。 实际上,有一个想法,就是让我们收集它与收集它完全一样。 并尝试使用类似Docker的东西,因为它是这样的东西,可以将其放在本地的计算机上,它将变得简单,隔离,不接触任何东西,一切都很好。

但是这个问题与页面html有关,它与实际的一样。 一个想法诞生了。 您有一个简化的webpack.conf,从中有一些用于客户端js的EntryPoint。 描述了模块,如何组装它们,输出文件,您已经描述的所有插件,已配置的一切,一切都很好。

如果我这样做怎么办? 他将进入我的组件并孤立地收集它。 而且将只有一个组成部分。 如果我在此处添加html webpack,它也会给我html,这些资产将在此处收集,但是,此东西已经可以自动进行测试。
我本来要写所有这些,但是后来我发现了。

Jest-puppeteer-React,一个年轻的插件。 并且我开始为它做出积极的贡献。 如果您突然想尝试一下,可以例如来找我,我可以提供某种帮助。 实际上,该项目不是我的。
您将常规文件编写为test.js,并且需要单独编写这些文件以帮助找到它们,以便不为您编译整个项目,而仅编译必需的组件。 实际上,您采用了webpack配置。 输入点将更改为这些browser.js文件,也就是说,我们要测试的文件将完全打包为html,在Puppeteer的帮助下,它将带您截屏。

他能做什么? , jest-image-snapshot. . , , js, media-query, , .
headless-, , , , , headless-, Chrome . web-, , , , .
Docker. . . , Docker, . . Docker , , , Linux, - , - . Docker , .

? , . , . before-after, , . , . , Chrome, Firefox. .
. pixelmatch. , looksame, «», . , .

— . , , . , : - , — Enzyme. Redux store . . viewport, , . , , .

. , . ? , .

: 5-10 . Selenium . , , , . .

Puppeteer, e2e-. , e2e- — , Selenium.
:
— , Selenium Java , . - JS Puppeteer, , .
, . , , .

— Selenium Java, — JS Puppeteer. . 18 . , , Java. , , Java Selenium.

:
— ? . , html-, css . e2e. . , .

, , . . , , — , . , . - , , : , .
, , . git hook, -, . green master — , , , . 谢谢啦