测试你的前端代码 – part3(端到端测试)

上一篇文章《测试你的前端代码 – part2(单元测试)》中,我介绍了关于单元测试的基本知识,从本文介绍端到端测试(E2E 测试)。

端到端测试

第二部分中,我们使用 Mocha 测试了应用中最核心的逻辑,calculator 模块。本文中我们将使用端到端测试整个应用,实际上是模拟了用户所有可能的操作进行测试。

在我们的例子中,计算器展示出来的前端即为整个应用,因为没有后端。所以端到端测试就是说直接在浏览器中运行应用,通过键盘做一系列计算操作,且保证所展示的输出结果都是正确的。

是否需要像单元测试那样,测试各种组合呢?并不是,我们已经在单元测试中测试过了,端到端测试不是检查某个单元是否 ok,而是把它们放到一起,检查还是否能够正确运行。

需要多少端到端测试

首先给出结论:端到端测试不需要太多。

第一个原因,如果已经通过了单元测试和集成测试,那么可能已经把所有的模块都测试过了。那么端到端测试的作用就是把所有的单元测试绑到一起进行测试,所以不需要很多端到端测试。

第二个原因,这类测试一般都很慢。如果像单元测试那样有几百个端到端测试,那运行测试将会非常慢,这就违背了一个很重要的测试原则——测试迅速反馈结果。

第三个原因,端到端测试的结果有时候会出现 flaky 的情况。Flaky 测试是指通常情况下可以测试通过,但是偶尔会出现测试失败的情况,也就是不稳定测试。单元测试几乎不会出现不稳定的情况,因为单元测试通常是简单输入,简单输出。一旦测试涉及到了 I/O,那么不稳定测试可能就出现了。那可以减少不稳定测试吗?答案是肯定的,可以把不稳定测试出现的频率减少到可以接受的程度。那能够彻底消除不稳定测试吗?也许可以,但是我到现在还没见到过[笑着哭]。

所以为了减少我们测试中的不稳定因素,尽量减少端到端测试。10 个以内的端到端测试,每个都测试应用的主要工作流。

写端到端测试代码

好了,废话不多说,开始介绍写端到端代码。首先需要准备好两件事情:1. 一个浏览器;2. 运行前端代码的服务器。

因为要使用 Mocha 进行端到端测试,就和之前单元测试一样,需要先对浏览器和 web 服务器进行一些配置。使用 Mocha 的 before 钩子设置初始化状态,使用 after 钩子清理测试后状态。before 和 after 钩子分别在测试的开始和结束时运行。

下面一起来看下 web 服务器的设置。

设置 Web 服务器

配置一个 Node Web 服务器,首先想到的就是 express 了,话不多说,直接上代码

代码中,before 钩子中创建一个 express 应用,指向 dist 文件夹,并且监听 8080 端口,结束的时候在 after 钩子中关闭服务器。

dist 文件夹是什么?是我们打包 JS 文件的地方(使用 Webpack打包),HTML 文件,CSS 文件也都在这里。可以看一下 package.json 的代码:

对于端到端测试,要记得在执行 npm test 之前,先执行 npm run build。其实这样很不方便,想一下之前的单元测试,不需要做这么复杂的操作,就是因为它可以直接在 node 环境下运行,既不用转译,也不用打包。

出于完整性考虑,看一下 webpack.config.js 文件,它是用来告诉 webpack 怎样处理打包:

上面的代码指的是,Webpack 会读取 app.js 文件,然后将 dist 文件夹中所有用到的文件都打包到 bundle.js 中。dist 文件夹会同时应用在生产环境和端到端测试环境。这里要注意一个很重要的事情,端到端测试的运行环境要尽量和生产环境保持一致。

设置浏览器

现在我们已经设置完了后端,应用已经有了服务器提供服务了,现在要在浏览器中运行我们的计算器应用。用什么包来驱动自动执行程序呢,我经常使用 selenium-webdriver,这是一个很流行的包。

首先看一下如何使用驱动:

before 中,准备好驱动,在 after 中把它清理掉。准备好驱动后,会自动运行浏览器(Chrome,稍后会看到),清理掉以后会关闭浏览器。这里注意,准备驱动的过程是异步的,返回一个 promise,所以我们使用 async/await 功能来使代码看起来更美观(Node7.7,第一个本地支持 async/await 的版本)。

最后在测试函数中,传递网址:http:/localhost:8080,还是使用 await,让 driver.get 成为异步函数。

你是否有好奇 prepareDrivercleanupDriver 函数长什么样呢?一起来看下:

可以看到,上面这段代码很笨重,而且只能在 Unix 系统上运行。理论上,你可以不用看懂,直接复制/粘贴到你的测试代码中就可以了,这里我还是深入讲一下。

前两行引入了 webdriver 和我们使用的浏览器驱动 chromedriver。Selenium Webdriver 的工作原理是通过 API(第一行中引入的 selenium-webdriver)调用浏览器,这依赖于被调浏览器的驱动。本例中被调浏览器驱动是 chromedriver,在第二行引入。

chrome driver 不需要在机器上装了 Chrome,实际上在你运行 npm install 的时候,已经装了它自带的可执行 Chrome 程序。接下来 chromedriver 的目录名需要添加进环境变量中,见代码中的第 9 行,在清理的时候再把它删掉,见代码中第 22 行。

设置了浏览器驱动以后,我们来设置 web driver,见代码的 11 – 15 行。因为 build 函数是异步的,所以它也使用 await。到现在为止,驱动部分就已经设置完毕了。

测试吧!

设置完驱动以后,该看一下测试的代码了。完整的测试代码在这里,下面列出部分代码:

这里的代码调用计算器应用,检查应用标题是不是 “Calculator”。代码中第 6 行,给浏览器赋地址:http://localhost:8080,记得要使用 await。再看第 9 行,调用浏览器并且返回浏览器的标题,在第 10 行中与预期的标题进行比较。

这里还有一个问题,这里引入了 promise-retry 模块进行重试,为什么需要重试?原因是这样的,当我们告诉浏览器执行某命令,比如定位到一个 URL,浏览器会去执行,但是是异步执行。浏览器执行的非常快,这时候对于开发人员来讲,确切地知道浏览器“正在执行”,要比仅仅知道一个结果更重要。正是因为浏览器执行的非常快,所以如果不重试的话,很容易被 await 所愚弄。在后面的测试中 promise-retry 也会经常使用,这就是为什么在端到端测试中需要重试的原因。

测试 Element

来看测试的下一阶段,测试元素:

下一个要测试的是初始化状态下所显示的是不是 “0”,那么首先就需要找到控制显示的 element,在我们的例子中是 display。见第 7 行代码,webdriver 的 findElement 方法返回我们所要找的元素。可以通过 By.id或者 By.css 再或者其他找元素的方法。这里我使用 By.css,它很常用,另外提一句 By.javascript 也很常用。

(不知道你是否注意到,By 是由最上面的 selenium-webdriver 所引入的)

当我们获取到了 element 以后,就可以使用 getText()(还可以使用其他操作 element 的函数),来获取元素文本,并且检查它是否和预期一样,见第 10 行。对了,不要忘记:

测试 UI

现在该来从 UI 层面测试应用了,点击数字和操作符,测试计算器是不是按照预期的运行:

代码 2 – 4 行,定义数字和操作;6 – 10 行模拟点击。实际上想实现的是 “42 * 2 = ”。最终获得正确的结果——“84”。

运行测试

已经介绍完了端到端测试和单元测试,现在用 npm test 来运行所有测试:

一次性全部通过!(这是当然的了,不然怎么写文章。)

想说点关于使用 await 的一些话

你在可能网络上其他地方看到一些例子,它们并没有使用 async/await,或者是使用了 promise。实际上这样的代码是同步的。那么为什么也能 work 的很好呢?坦白地说,我也不知道,看起来像是在 webdriver 中有些 trick 的处理。正如 selenium 文档中说道,在 Node 支持 async/await 之前,这是一个临时的解决方案。

Selenium 文档是 Java 语言。它还不完整,但是包含的信息也足够了,你做几次测试就能掌握这个技能。

总结

本文中主要介绍了什么:

  • 介绍了端到端测试中设置浏览器的代码;
  • 介绍了如何使用 webdriver API 来调用浏览器,以及如何获取 DOM 中的 element;
  • 介绍了使用 async/await,因为所有 webdriver API 都是异步的;
  • 介绍了为什么端到端测试中要使用 retry。
1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部