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

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

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

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

《测试你的前端代码 - 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 了,话不多说,直接上代码

    let server
      
      before((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 += chromeDriverPathAddition
    
      return 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 行。对了,不要遗忘:

《测试你的前端代码 - part3(端到端测试)》

测试 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 来运转一切测试:

《测试你的前端代码 - part3(端到端测试)》

一次性悉数经由历程!(这是固然的了,不然怎样写文章。)

想说点关于运用 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 感兴趣的童鞋,迎接指导

    原文作者:胡子大哈
    原文地址: https://segmentfault.com/a/1190000008915567
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞