本文作者:Gil Tayar
编译:胡子大哈

翻译原文:http://huziketang.com/blog/posts/detail?postId=58d50da37413fc2e8240855c
英文连接:Testing Your Frontend Code: Part III (E2E Testing)

转载请注明出处,保留原文链接以及作者信息

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

端到端测试

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

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

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

需要多少端到端测试

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

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

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

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

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

写端到端测试代码

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

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

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

设置 Web 服务器

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

    let serverbefore((done) => {const app = express()app.use('/', express.static(path.resolve(__dirname, '../../dist')))server = app.listen(8080, done)})after(() => {server.close()})

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

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

    {"name": "frontend-testing","scripts": {"build": "webpack && cp public/* dist","test": "mocha 'test/**/test-*.js' && eslint test lib",...},

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

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

    module.exports = {entry: './lib/app.js',output: {filename: 'bundle.js',path: path.resolve(__dirname, 'dist')},...}

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

设置浏览器

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

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

    const {prepareDriver, cleanupDriver} = require('../utils/browser-automation')//...describe('calculator app', function () {let driver...before(async () => {driver = await prepareDriver()})after(() => cleanupDriver(driver))it('should work', async function () {await driver.get('http://localhost:8080')//...}) })

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

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

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

    const webdriver = require('selenium-webdriver')const chromeDriver = require('chromedriver')const path = require('path')const chromeDriverPathAddition = `:${path.dirname(chromeDriver.path)}`exports.prepareDriver = async () => {process.on('beforeExit', () => this.browser && this.browser.quit())process.env.PATH += chromeDriverPathAdditionreturn await new webdriver.Builder().disableEnvironmentOverrides().forBrowser('chrome').setLoggingPrefs({browser: 'ALL', driver: 'ALL'}).build()}exports.cleanupDriver = async (driver) => {if (driver) {driver.quit()}process.env.PATH = process.env.PATH.replace(chromeDriverPathAddition, '')}

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

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

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

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

测试吧!

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

    // ...const retry = require('promise-retry')// ...it('should work', async function () {await driver.get('http://localhost:8080')await retry(async () => {const title = await driver.getTitle()expect(title).to.equal('Calculator')})//...

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

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

测试 Element

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

    const {By} = require('selenium-webdriver')it('should work', async function () {await driver.get('http://localhost:8080')//...await retry(async () => {const displayElement = await driver.findElement(By.css('.display'))const displayText = await displayElement.getText()expect(displayText).to.equal('0')})//...

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

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

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

测试 UI

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

     const digit4Element = await driver.findElement(By.css('.digit-4'))const digit2Element = await driver.findElement(By.css('.digit-2'))const operatorMultiply = await driver.findElement(By.css('.operator-multiply'))const operatorEquals = await driver.findElement(By.css('.operator-equals'))await digit4Element.click()await digit2Element.click()await operatorMultiply.click()await digit2Element.click()await operatorEquals.click()await retry(async () => {const displayElement = await driver.findElement(By.css('.display'))const displayText = await displayElement.getText()expect(displayText).to.equal('84')})

代码 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。

下文简介

介绍完了端到端测试,下文该介绍“测试光谱”的中间部分了,即集成测试。链接直达:《测试你的前端代码 - part4(集成测试)》。


我最近正在写一本《React.js 小书》,对 React.js 感兴趣的童鞋,欢迎指点。

测试你的前端代码 - part3(端到端测试)相关推荐

  1. python移动端web开发代码_移动web前端开发,前端开发工作总结,移动端页面开发-我主页-一个前端程序猿的博客...

    热门推荐 html/css 一.escape和它们不是同一类简单来说,escape是对字符串(string)进行编码(而另外两种是对URL),作用是让它们在所有电脑上可读.编码之后的... 标签: 0 ...

  2. python写前端代码_python前端学习之移动端和bootstrap

    我们为什么要写自适应的页面(响应式页面) 众所周知,电脑.平板.手机的屏幕是差距很大的,假如在电脑上写好了一个页面,在电脑上看起来不错,但是如果放到手机上的话,那可能就会乱的一塌糊涂,这时候怎么解决呢 ...

  3. 前端每周清单第 36 期:深入 Vue 2.5 类型增强、Puppeteer 端到端测试、PayPal 跨域套装...

    前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点:分为新闻热点.开发教程.工程实践.深度阅读.开源项目.巅峰人生等栏目.欢迎关注[前端之巅]微信公众号(ID:front ...

  4. 在线html代码测试工具,CodePen - 一个在线的前端代码编辑工具(可用于制作测试页面、代码调试)...

    有时我们需要调试一些前端代码(无论是 html.css,还是 js),或者要制作一个 demo 分享给他人.这些都可以借助 CodePen 这个网站来实现. 一.基本介绍 1,什么是 CodePen? ...

  5. 最佳实践系列:前端代码标准和最佳实践

    最佳实践系列:前端代码标准 @窝窝商城前端(刘轶/李晨/徐利/穆尚)翻译于2012年 版本0.55 @郑昀校对 isobar的这个前端代码标准和最佳实践文档,涵盖了Web应用开发的方方面面,我们翻译了 ...

  6. 前端代码是怎样智能生成的?

    简介: 作为阿里经济体前端委员会四大技术方向之一,前端智能化项目经历了 2019 双十一的阶段性考验,交出了不错的答卷,天猫淘宝双十一会场新增模块 79.34% 的线上代码由前端智能化项目自动生成.在 ...

  7. 我与无影的初体验:使用无影云桌面进行一个开源 Angular 项目的端到端测试

    近日很荣幸地收到了阿里云邀请做一个关于阿里旗下无影云桌面的评测,从官网上了解到阿里云无影云桌面原名为弹性云桌面,融合了无影产品技术后更名升级,可广泛应用于具有高数据安全管控.高性能计算等要求的安全办公 ...

  8. 端到端测试实践:Jenkins集成TestCafe

    上一篇<对产品质量的一点思考>中说到自动化测试的重要性,本文简单介绍下怎样在实际项目中实现端到端测试的自动化,在这里我们使用的端到端测试工具是TestCafe. 环境 Jenkisn:2. ...

  9. 代码逻辑分析_双11模块79.34%的前端代码是怎样智能生成的?

    作者|妙净.波本最近几年,AI 渗透到开发领域的方方面面.在前端领域,设计稿生成代码(简称 D2C)就是一个例子.其目标在于通过 AI 助力前端提效升级,杜绝简单重复性的工作内容.今年"双十 ...

最新文章

  1. 转从Qt4 到Qt5的变化
  2. 给你一个网站你是如何来渗透测试的
  3. linux开发板加快开机速度,readahead加速Linux开机速度
  4. openlayers基础系列教程(一)
  5. res.status === 200含义
  6. 1.mongoDB 简介
  7. Atitit 品牌之道 attilax著 艾龙 著 1. 第1章 品牌和品牌管理 1 2. 第Ⅱ篇 制定品牌战略 2 3. 第Ⅲ篇 品牌营销活动:设计与执行 2 4. 第Ⅳ篇 评估和诠释品牌绩效 3
  8. 社交网络图形可视化工具Gephi使用教程
  9. Linux内核学习篇三:中断处理 -- asm.s和traps.c
  10. 3.5正交试验设计法
  11. 苹果计算机的优势,苹果笔记本的优缺点详细分析
  12. 算法面试和实习经验分享
  13. 机器学习之朴素贝叶斯、贝叶斯信念网络
  14. VM虚拟机安装无法打开注册表项及虚拟网卡消失导致网络出错等问题
  15. (附源码)基于PHP的酒店住宿管理系统 毕业设计 261455
  16. 设计模式(十四)中介者模式
  17. step(stp)文件导入ANSYS 2020 R2 workbench Geometry的方法
  18. 呀,葵花宝典![IT项目经理成长晋升记2]
  19. oracle之汉字转拼音
  20. 最新数据挖掘赛事方案梳理!

热门文章

  1. python获取按键状态_谁在用 python 弹奏一曲菊花台
  2. 生成器、生成器函数、推导式、生成器表达式
  3. 借鉴开源框架自研日志收集系统
  4. Openssl rand命令
  5. i2c的时钟延展问题(转)
  6. 看到他我一下子就悟了-- Lambda表达式
  7. 让你大脑变冷静的28句英文
  8. java 代码性能优化_Java代码性能优化(四)
  9. 三星二级菜单_你变我也变,神奇的excel二级联动下拉菜单
  10. 在ubuntu16.04中安装apache2+modsecurity以及自定义WAF规则详解