国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

測試你的前端代碼 - part3(端到端測試)

ixlei / 2934人閱讀

摘要:單元測試幾乎不會出現(xiàn)不穩(wěn)定的情況,因為單元測試通常是簡單輸入,簡單輸出。鏈接直達(dá)測試你的前端代碼集成測試。

本文作者:Gil Tayar
編譯:胡子大哈

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

轉(zhuǎn)載請注明出處,保留原文鏈接以及作者信息

上一篇文章《測試你的前端代碼 - part2(單元測試)》中,我介紹了關(guān)于單元測試的基本知識,從本文介紹端到端測試(E2E 測試)。

端到端測試

在第二部分中,我們使用 Mocha 測試了應(yīng)用中最核心的邏輯,calculator 模塊。本文中我們將使用端到端測試整個應(yīng)用,實際上是模擬了用戶所有可能的操作進(jìn)行測試。

在我們的例子中,計算器展示出來的前端即為整個應(yīng)用,因為沒有后端。所以端到端測試就是說直接在瀏覽器中運行應(yīng)用,通過鍵盤做一系列計算操作,且保證所展示的輸出結(jié)果都是正確的。

是否需要像單元測試那樣,測試各種組合呢?并不是,我們已經(jīng)在單元測試中測試過了,端到端測試不是檢查某個單元是否 ok,而是把它們放到一起,檢查還是否能夠正確運行。

需要多少端到端測試

首先給出結(jié)論:端到端測試不需要太多。

第一個原因,如果已經(jīng)通過了單元測試和集成測試,那么可能已經(jīng)把所有的模塊都測試過了。那么端到端測試的作用就是把所有的單元測試綁到一起進(jìn)行測試,所以不需要很多端到端測試。

第二個原因,這類測試一般都很慢。如果像單元測試那樣有幾百個端到端測試,那運行測試將會非常慢,這就違背了一個很重要的測試原則——測試迅速反饋結(jié)果。

第三個原因,端到端測試的結(jié)果有時候會出現(xiàn) flaky 的情況。Flaky 測試是指通常情況下可以測試通過,但是偶爾會出現(xiàn)測試失敗的情況,也就是不穩(wěn)定測試。單元測試幾乎不會出現(xiàn)不穩(wěn)定的情況,因為單元測試通常是簡單輸入,簡單輸出。一旦測試涉及到了 I/O,那么不穩(wěn)定測試可能就出現(xiàn)了。那可以減少不穩(wěn)定測試嗎?答案是肯定的,可以把不穩(wěn)定測試出現(xiàn)的頻率減少到可以接受的程度。那能夠徹底消除不穩(wěn)定測試嗎?也許可以,但是我到現(xiàn)在還沒見到過[笑著哭]。

所以為了減少我們測試中的不穩(wěn)定因素,盡量減少端到端測試。10 個以內(nèi)的端到端測試,每個都測試應(yīng)用的主要工作流。

寫端到端測試代碼

好了,廢話不多說,開始介紹寫端到端代碼。首先需要準(zhǔn)備好兩件事情:1. 一個瀏覽器;2. 運行前端代碼的服務(wù)器。

因為要使用 Mocha 進(jìn)行端到端測試,就和之前單元測試一樣,需要先對瀏覽器和 web 服務(wù)器進(jìn)行一些配置。使用 Mocha 的 before 鉤子設(shè)置初始化狀態(tài),使用 after 鉤子清理測試后狀態(tài)。before 和 after 鉤子分別在測試的開始和結(jié)束時運行。

下面一起來看下 web 服務(wù)器的設(shè)置。

設(shè)置 Web 服務(wù)器

配置一個 Node Web 服務(wù)器,首先想到的就是 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 鉤子中創(chuàng)建一個 express 應(yīng)用,指向 dist 文件夾,并且監(jiān)聽 8080 端口,結(jié)束的時候在 after 鉤子中關(guān)閉服務(wù)器。

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

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

對于端到端測試,要記得在執(zhí)行 npm test 之前,先執(zhí)行 npm run build。其實這樣很不方便,想一下之前的單元測試,不需要做這么復(fù)雜的操作,就是因為它可以直接在 node 環(huán)境下運行,既不用轉(zhuǎn)譯,也不用打包。

出于完整性考慮,看一下 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 文件夾會同時應(yīng)用在生產(chǎn)環(huán)境和端到端測試環(huán)境。這里要注意一個很重要的事情,端到端測試的運行環(huán)境要盡量和生產(chǎn)環(huán)境保持一致。

設(shè)置瀏覽器

現(xiàn)在我們已經(jīng)設(shè)置完了后端,應(yīng)用已經(jīng)有了服務(wù)器提供服務(wù)了,現(xiàn)在要在瀏覽器中運行我們的計算器應(yīng)用。用什么包來驅(qū)動自動執(zhí)行程序呢,我經(jīng)常使用 selenium-webdriver,這是一個很流行的包。

首先看一下如何使用驅(qū)動:

    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 中,準(zhǔn)備好驅(qū)動,在 after 中把它清理掉。準(zhǔn)備好驅(qū)動后,會自動運行瀏覽器(Chrome,稍后會看到),清理掉以后會關(guān)閉瀏覽器。這里注意,準(zhǔn)備驅(qū)動的過程是異步的,返回一個 promise,所以我們使用 async/await 功能來使代碼看起來更美觀(Node7.7,第一個本地支持 async/await 的版本)。

最后在測試函數(shù)中,傳遞網(wǎng)址:http:/localhost:8080,還是使用 await,讓 driver.get 成為異步函數(shù)。

你是否有好奇 prepareDrivercleanupDriver 函數(shù)長什么樣呢?一起來看下:

    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 系統(tǒng)上運行。理論上,你可以不用看懂,直接復(fù)制/粘貼到你的測試代碼中就可以了,這里我還是深入講一下。

前兩行引入了 webdriver 和我們使用的瀏覽器驅(qū)動 chromedriver。Selenium Webdriver 的工作原理是通過 API(第一行中引入的 selenium-webdriver)調(diào)用瀏覽器,這依賴于被調(diào)瀏覽器的驅(qū)動。本例中被調(diào)瀏覽器驅(qū)動是 chromedriver,在第二行引入。

chrome driver 不需要在機器上裝了 Chrome,實際上在你運行 npm install 的時候,已經(jīng)裝了它自帶的可執(zhí)行 Chrome 程序。接下來 chromedriver 的目錄名需要添加進(jìn)環(huán)境變量中,見代碼中的第 9 行,在清理的時候再把它刪掉,見代碼中第 22 行。

設(shè)置了瀏覽器驅(qū)動以后,我們來設(shè)置 web driver,見代碼的 11 - 15 行。因為 build 函數(shù)是異步的,所以它也使用 await。到現(xiàn)在為止,驅(qū)動部分就已經(jīng)設(shè)置完畢了。

測試吧!

設(shè)置完驅(qū)動以后,該看一下測試的代碼了。完整的測試代碼在這里,下面列出部分代碼:

    // ...
    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")
        })
        //...

這里的代碼調(diào)用計算器應(yīng)用,檢查應(yīng)用標(biāo)題是不是 “Calculator”。代碼中第 6 行,給瀏覽器賦地址:http://localhost:8080,記得要使用 await。再看第 9 行,調(diào)用瀏覽器并且返回瀏覽器的標(biāo)題,在第 10 行中與預(yù)期的標(biāo)題進(jìn)行比較。

這里還有一個問題,這里引入了 promise-retry 模塊進(jìn)行重試,為什么需要重試?原因是這樣的,當(dāng)我們告訴瀏覽器執(zhí)行某命令,比如定位到一個 URL,瀏覽器會去執(zhí)行,但是是異步執(zhí)行。瀏覽器執(zhí)行的非??欤@時候?qū)τ陂_發(fā)人員來講,確切地知道瀏覽器“正在執(zhí)行”,要比僅僅知道一個結(jié)果更重要。正是因為瀏覽器執(zhí)行的非???,所以如果不重試的話,很容易被 await 所愚弄。在后面的測試中 promise-retry 也會經(jīng)常使用,這就是為什么在端到端測試中需要重試的原因。

測試 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")
        })
        
        //...

下一個要測試的是初始化狀態(tài)下所顯示的是不是 “0”,那么首先就需要找到控制顯示的 element,在我們的例子中是 display。見第 7 行代碼,webdriver 的 findElement 方法返回我們所要找的元素。可以通過 By.id 或者 By.css 再或者其他找元素的方法。這里我使用 By.css,它很常用,另外提一句 By.javascript 也很常用。

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

當(dāng)我們獲取到了 element 以后,就可以使用 getText()(還可以使用其他操作 element 的函數(shù)),來獲取元素文本,并且檢查它是否和預(yù)期一樣,見第 10 行。對了,不要忘記:

測試 UI

現(xiàn)在該來從 UI 層面測試應(yīng)用了,點擊數(shù)字和操作符,測試計算器是不是按照預(yù)期的運行:

     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 行,定義數(shù)字和操作;6 - 10 行模擬點擊。實際上想實現(xiàn)的是 “42 * 2 = ”。最終獲得正確的結(jié)果——“84”。

運行測試

已經(jīng)介紹完了端到端測試和單元測試,現(xiàn)在用 npm test 來運行所有測試:

一次性全部通過?。ㄟ@是當(dāng)然的了,不然怎么寫文章。)

想說點關(guān)于使用 await 的一些話

你在可能網(wǎng)絡(luò)上其他地方看到一些例子,它們并沒有使用 async/await,或者是使用了 promise。實際上這樣的代碼是同步的。那么為什么也能 work 的很好呢?坦白地說,我也不知道,看起來像是在 webdriver 中有些 trick 的處理。正如 selenium 文檔中說道,在 Node 支持 async/await 之前,這是一個臨時的解決方案。

Selenium 文檔是 Java 語言。它還不完整,但是包含的信息也足夠了,你做幾次測試就能掌握這個技能。

總結(jié)

本文中主要介紹了什么:

介紹了端到端測試中設(shè)置瀏覽器的代碼;

介紹了如何使用 webdriver API 來調(diào)用瀏覽器,以及如何獲取 DOM 中的 element;

介紹了使用 async/await,因為所有 webdriver API 都是異步的;

介紹了為什么端到端測試中要使用 retry。

下文簡介

介紹完了端到端測試,下文該介紹“測試光譜”的中間部分了,即集成測試。鏈接直達(dá):《測試你的前端代碼 - part4(集成測試)》。


我最近正在寫一本《React.js 小書》,對 React.js 感興趣的童鞋,歡迎指點。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/8753.html

相關(guān)文章

  • 測試你的前端代碼 - part3到端測試

    摘要:單元測試幾乎不會出現(xiàn)不穩(wěn)定的情況,因為單元測試通常是簡單輸入,簡單輸出。鏈接直達(dá)測試你的前端代碼集成測試。 本文作者:Gil Tayar 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/posts/detail?postId=58d50da37413fc2e8240855c 英文連接:Testing Your Frontend Code: Part ...

    libxd 評論0 收藏0
  • 測試你的前端代碼 - part1(介紹篇)

    摘要:測試光譜光譜的一端單元測試顧名思義,代碼以單元為單位進(jìn)行測試。這個系列文章整體如下測試你的前端代碼單元測試測試你的前端代碼端到端測試測試你的前端代碼集成測試。 showImg(https://segmentfault.com/img/remote/1460000008812278?w=998&h=354); 本文作者:Gil Tayar 編譯:胡子大哈 翻譯原文:http://hu...

    helloworldcoding 評論0 收藏0
  • 測試你的前端代碼 - part1(介紹篇)

    摘要:測試光譜光譜的一端單元測試顧名思義,代碼以單元為單位進(jìn)行測試。這個系列文章整體如下測試你的前端代碼單元測試測試你的前端代碼端到端測試測試你的前端代碼集成測試。 showImg(https://segmentfault.com/img/remote/1460000008812278?w=998&h=354); 本文作者:Gil Tayar 編譯:胡子大哈 翻譯原文:http://hu...

    jimhs 評論0 收藏0
  • 測試你的前端代碼 - part2(單元測試

    摘要:單元測試上一節(jié)有討論過,單元測試就是以代碼單元為單位進(jìn)行測試,代碼單元可以是一個函數(shù),一個模塊,或者一個類。單元測試是最容易理解也最容易實現(xiàn)的測試方式。在寫單元測試的時候,盡量將你的單元測試獨立出來,不要幾個單元互相引用。 showImg(https://segmentfault.com/img/remote/1460000008823416?w=997&h=350); 本文作者:G...

    daydream 評論0 收藏0
  • 測試你的前端代碼 - part2(單元測試

    摘要:單元測試上一節(jié)有討論過,單元測試就是以代碼單元為單位進(jìn)行測試,代碼單元可以是一個函數(shù),一個模塊,或者一個類。單元測試是最容易理解也最容易實現(xiàn)的測試方式。在寫單元測試的時候,盡量將你的單元測試獨立出來,不要幾個單元互相引用。 showImg(https://segmentfault.com/img/remote/1460000008823416?w=997&h=350); 本文作者:G...

    shadajin 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<