• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • 淺談如何進行有效的前端測試

    發表于:2018-07-03來源:未知作者:未知點擊數: 標簽:前端測試
    前端測試或許被好多人誤解,也許大家更加傾向于編寫面向后端的測試,邏輯性強,測試方便等聊到這導致了好多前端從來不寫測試(測試全靠手點~~~)其實沒必要達到測試驅

    前端測試或許被好多人誤解,也許大家更加傾向于編寫面向后端的測試,邏輯性強,測試方便等

    聊到這導致了好多前端從來不寫測試(測試全靠手點~~~)

    其實沒必要達到測試驅動開發的程度,只要寫完代碼可以補測試,并且補出高效的測試,前端或許真的不需要手點

    大前端時代不談環境不成方圓,本文從下面幾個環境一一分析下如何敏捷測試

    • node 環境
    • vue 環境
    • nuxt 服務端渲染環境
    • react 環境
    • next 服務端渲染環境
    • angular 環境

    理解測試前需要補充下單元測試(unit)和端到端測試(e2e)的概念,這里不贅述

    node 環境

    推薦測試框架jest

    jest 是 FB 的杰作之一,方便各種場景的 js 代碼測試,這里選擇 jest 是因為確實方便

    使用方法及配置信息可以去官方文檔

    配置的注意事項

    {
      testEnvironment: 'node' // 如不聲明默認瀏覽器環境
    }

    針對 node 只聊一下單元測試,e2e 測試比較少見

    當決定寫一個 npm 模塊時,代碼完成后必不可少的就是單元測試,單元測試需要注意的問題比較瑣碎

    mock

    當引入三方庫時,不得不 mock 數據,因為單元測試更多講求的是局部測試,不要受外界三方引入包的影響

    例如:

    const { readFileSync } = require('fs')
    
    const getFile = () => {
      try {
        const text = readFileSync('text.txt', 'utf8')
      } catch (err) {
        throw new Error(err)
      }
    
      console.log(text)
    }
    
    module.exports = getFile

    這時我們并不需要關心 text.txt 是否真的存在,也不需要關系 text 的內容具體是什么,我們的關注點應該在于讀取文件錯誤時能否及時拋出異常,以及 console.log() 是否如預期執行

    對應到測試

    const getFile = require('./getFile')
    
    describe('readFile', () => {
      const mocks = {
        fs: {
          readFileSync: jest.fn()
        },
        other: {
          text: 'Test text'
        }
      }
    
      beforeAll(() => {
        jest.mock('fs', () => mocks.fs)
      })
    
      test('read file success run console.log', () => {
        mocks.fs.readFileSync.mockImplementation(() => this.mocks.other.text)
    
        getFile()
    
        expect(console.log).toBeCalled()
      })
    })

    上面代碼簡單的實現了一個讀取文件是否成功的測試,先別急著糾錯,這段測試本身是錯的,下面慢慢分析

    我們在最開始創建了一個 mocks 對象,用來模擬數據,由于 readFileSync 方法可能存在多種返回結果(成功或報錯),所以暫時用 jest.fn() 模擬

    other 里面則是放一些固定的測試數據(不會隨著測試過程而改變)

    beforeAll 鉤子里面執行我們的 mock,把 require 進來的 fs 模塊攔截調,也是本測試用例中的關鍵步驟

    在第一個 test 里面我們改寫 mocks.fs.readFileSync 的返回形式,這里使用的 mockImplementation 是直接模擬了一個執行函數,當然也可以模擬返回值,具體可以到 jest 官網

    expect 用來斷言我們的 console.log 方法執行了

    解釋了這么多測試新手們應該也都看的明白了,下面聊一下錯在哪,怎么改進

    1. mockImplementation 最好替換為 mockReturnValueOnce ,注意這里出現了 Once 結尾,也就是僅模擬一次返回值, mockImplementation 最好使用在復雜場景,所謂的復雜就是我們手動實現一個 readFileSync 方法使得測試達到我們預期的目的,在這個簡單的場景里面我們只需要模擬返回值就好
    2. expect(console.log) 這里會報錯,因為 jest 斷言的內容只能是 mock function 或 spy,這里 console 是全局對象 global 上的方法,我們沒有 require 將其引入,所以 jest.mock 顯然處理上有些吃力,這時候 spy 就派上用場了, beforeAll 鉤子里直接執行 jest.spyOn(global.console, 'log') ,接下來我們就能監聽到 console.log 的執行了 expect(global.console.log)
    3. 斷言的目的是測試 console.log 的執行,這是不嚴謹的測試,我們需要使用 toBeCalledWith來代替 toBeCalled ,不僅要測試執行了,而且要測試參數正確,簡單修改為 expect(global.console.log).toBeCalledWith(this.mocks.other.text)

    下面補一下 read file 失敗的測試

    test('read file fail throw error', () => {
      mocks.fs.readFileSync.mockImplementationOnce(() => { throw new Error('readFile error') })
    
      expect(getFile()).toThrow()
      expect(global.console.log).not.toBeCalled()
    })

    讀取文件失敗的測試就好理解的多,注意的就是對一個 jest.fn() 多次進行修改會導致測試用例之間的相互影響,這里盡量使用 Once 結尾方法,復雜場景可以如下

    beforeEach(() => {
      mocks.fs.readFileSync.mockReset()
    })

    每次執行 test 前先清除 mock,避免多個測試用例之間復雜化 mock 導致錯誤

    小結:單元測試中的 mock 是個測試思路,我們無需關心外部文件和依賴是什么,只要能模擬出正確的情況程序是否按規則執行,錯誤的情況程序是否有異常處理,邏輯是否正確等。這樣就能排除外界干擾,使得我們測試的當前一小部分是可靠的,穩定的即可。

    引用外部文件

    單拿出一個小結說下 require 的問題,node 9 之前不支持 es6 的 import,這里也不詳細說明了。

    require 本身并不復雜,但是如果搞不清楚執行時機,那么測試將無法進行,來一個例子

    const env = process.env.NODE_ENV
    
    module.export = () => env

    測試如下

    const getEnv = require('./getEnv')
    
    describe('env', () => {
      test('env will be dev', () => {
        process.env.NODE_ENV = 'dev'
    
        expect(getEnv()).toBe('dev')
      })
    
      test('env will be pord', () => {
        process.env.NODE_ENV = 'pord'
    
        expect(getEnv()).toBe('pord')
      })
    })

    十分簡單的測試,拋開了 mock 的流程,這里會報測試未通過,原因是 require 同時 env 已經被賦值為 undefined ,我們再試著改變 NODE_ENV 環境變量時,程序不會再次執行,當然了,處理起來也十分簡單

    let getEnv
    
    test('env will be dev', () => {
      process.env.NODE_ENV = 'dev'
      getEnv = require('./getEnv')
    
      expect(getEnv()).toBe('dev')
    })
    
    test('env will be pord', () => {
      process.env.NODE_ENV = 'pord'
      getEnv = require('./getEnv')
    
      expect(getEnv()).toBe('pord')
    })

    順帶說了一下,希望大家不要在這種低級錯誤上浪費時間

    其實引用外部文件還有些場景會對測試帶來困惑,比如動態路徑,場景如下

    const packageFile = `${process.cwd()}/package.json`
    
    const package = require(packageFile)

    讀取當前路徑下的 package.json ,當測試真正跑到這段代碼時會到當前目錄下找 package.json ,這里盡量 mock 掉 package.json 為我們自己的模擬數據,但是 jest 不支持動態路徑的 mock,試著這樣寫 jest.mock( ${process.cwd()}/package.json , () => mockFile) 會報錯,所以盡量使用可以 mock 的方案,保證單元測試可以順利進行,修改如下

    const path = require('path')
    
    const filePath = path.join(process.cwd(), 'package.json')

    這樣就可以 mock, path 了,和上面 mock 章節,大致思想都差不多

    覆蓋率

    單元測試覆蓋率不達標等于白測,測試過程盡量覆蓋所有判斷條件,而不是全部通過了就不管了,在進一階說,100% 的測試覆蓋率并不證明一定覆蓋到位了,因為順帶執行的代碼也會算進覆蓋率,例如

    module.export = (list) => list.map(({ id }) => id)

    我們先不考慮這個 list 類型是不是數組,只是簡單的例子,避免過度設計帶來復雜化,我們測試可以這樣

    const getId = require('./getId')
    const mocks = {
      list: [{
        id: 1,
        name: 'vue'
      }, {
        id: 2,
        name: 'react'
      }]
    }
    
    test('return id', () => {
      expect(getId(mocks.list)).toEqual([1, 2])
    })

    直到有一天代碼變成了 module.export = (list) => [1, 2]

    這時候測試還能通過,并且覆蓋率 100%,的確不會有人蠢到把代碼改成這樣,只是一個例子,實際上邏輯會比這個復雜的多

    那就聊一聊解決方案

    • mock 數據的隨機化,每次測試生成隨機的 list 進行測試,現有庫 mockjs
    • 強關聯測試,證明 map 方法的確執行了,并且參數正確,先 spy spyOn(Array.prototype, 'map') 然后斷言

    聊了一圈從覆蓋率聊到了測試健壯性的問題,可以思考下寫過的測試是否真的滿足注釋或修改任何一行代碼都能引起測試的 pass 報錯

    關于 node 就聊這么多,其實下文主要思想都一樣,更多的是介紹些簡單可行的方案,以及可能會踩坑的地方

    vue 環境

    在 vue 使用場景下,無非就是組件庫和業務邏輯,組件庫偏向于 unit 測試,業務邏輯偏向于 e2e 測試,當然兩者并不沖突

    unit 測試

    推薦神器: vue-test-utils

    README 給了多個測試庫配置的例子,這里還是推薦使用 jest,給個例子

    export default {
      props: ['value'],
      data () {
        return {
          currentValue: 0
        }
      },
      watch: {
        value (val) {
          this.currentValue = val
        }
      }
    }

    測試如下

    import { mount } from '@vue/test-utils'
    import Test from './Test.vue'
    
    test('props value', () => {
      const options = { propsData: { value: 3 } }
    
      const wrapper = mount(Test)
    
      expect(wrapper.vm.currentValue).toBe(3)
    })

    十分簡單的例子,亮點在測試文件的 wrapper 上,通過 mount 方法創建了一個組件實例,創建過程中允許加入一些配置信息,甚至是 mock 組件中的 method 方法

    vue 單元測試的范圍僅限于數據流動是否正確,邏輯渲染是否正確(v-if v-show v-for),style 和 class 是否正確,我們并不需要關系這個組件在瀏覽器渲染中的位置,也不需要關系對其它組件會造成什么影響,只要保證組件本身正確即可,前面說的斷言,vue-test-utils 都能提供對應的方案,總體上節約很多測試成本

    e2e 測試

    也是推薦尤大基于最新腳手架的 @vue/cli-plugin-e2e-nightwatch

    e2e 測試的重點在于判斷真實 DOM 是否滿足預期要求,甚至很少出現 mock 場景,不可或缺的是一個瀏覽器運行環境,具體細節不贅述,可以看官方文檔。

    nuxt 服務端渲染環境

    nuxt 官方推薦 ava ,順勢帶出 ava 的方案

    unit 測試

    麻煩在配置上面,先給出需要安裝的依賴

    "@vue/test-utils",
    "ava",
    "browser-env",
    "require-extension-hooks",
    "require-extension-hooks-babel",
    "require-extension-hooks-vue",
    "sinon"

    在 package.json 里加幾行 ava 配置

    "ava": {
      "require": [
        "./tests/helpers/setup.js"
      ]
    }

    下面來寫 ./tests/helpers/setup.js

    const hooks = require('require-extension-hooks')
    
    // Setup browser environment
    require('browser-env')()
    
    // Setup vue files to be processed by `require-extension-hooks-vue`
    hooks('vue').plugin('vue').push()
    // Setup vue and js files to be processed by `require-extension-hooks-babel`
    hooks(['vue', 'js']).plugin('babel').push()

    上面的代碼唯獨沒看到 sinon 這個庫,說到 ava 是沒有 mock 功能的,這就給單元測試的 mock 帶來巨大困難,不過我們可以通過引入 sinon 來解決 mock 數據的問題,在 mock 方面上 sinon 做的比 jest 還要優秀,支持沙箱模式,不影響外部數據

    給個簡單點的例子

    <template>
      <el-card v-for="item in topicList" :key="item.id">
        <div class="card-content">
          <span class="link" @click="toMember(item.member.username)">{{ item.member.username }}</span>
        </div>
      </el-card>
    </template>
    
    <script>
    export default {
      props: {
        topicList: {
          type: Array,
          required: true
        }
      },
      methods: {
        toMember (name) {
          this.$router.push(`/member/${name}`)
        }
      }
    }
    </script>

    對應的測試代碼如下

    import { shallowMount } from '@vue/test-utils'
    import test from 'ava'
    import sinon from 'sinon'
    
    test('methods: toMember', t => {
      const { topicList } = t.context
      const $router = {
        push: () => {}
      }
      const spy = sinon.spy($router, 'push')
    
      const wrapper = shallowMount(TopicListChalk, {
        propsData: { topicList },
        mocks: {
          $router
        }
      })
    
      topicList.forEach((item, index) => {
        const toMemberText = wrapper.findAll('.card-content').at(index).find('.link')
    
        toMemberText.trigger('click')
    
        t.true(spy.withArgs(`/member/${item.member.username}`).calledOnce)
      })
    })

    這里直接將 $router mock 掉,并且使用 sinon.spy 監聽執行,至于 this.$router.push 后瀏覽器有沒有跳轉并不是單元測試需要關心的,這里的寫法也比較特別,test 方法在回調里默認參數為 t ,對應的方法都掛載在 t 對象上,上下文可通過 t.context 傳遞

    nuxt 單元測試相關就聊這么多

    e2e 測試

    這里有個歧義點,nuxt 官網只給出了 e2e 的測試案例end-to-end-testing

    當使用默認腳手架構建的項目,也就是沒有 server 端入口文件的項目,這個方案確實可行

    但是涉及到其它框架(express|koa)的時候就顯得不夠用了,很有可能在自定義 server 入口是加入了大量中間件,這對于官網給出的例子是個巨大考驗,不可能在每個測試文件里實現一遍 new Nuxt ,所以需要更高層的封裝,也就是忽略 server 啟動流程的差異性,直接在瀏覽器中抓取頁面

    推薦: nuxt-jest-puppeteer

    react 環境

    unit 測試

    這一波沒得可選,jest 完勝,人家官網就有React,RN 的支持文檔

    文檔的案例也是十分全面,沒得講,不贅述

    e2e 測試

    其實上面講了兩個 e2e 的方案選擇,大同小異,需要一個能在 node 跑的無頭瀏覽器,官方沒有推薦,這里站 vue 一票選擇nightwatchjs

    next 服務端渲染環境

    unit 測試

    主要講一下如何配置,先是依賴包

    "babel-core",
    "babel-jest",
    "enzyme",
    "enzyme-adapter-react-16",
    "jest",
    "react-addons-test-utils",
    "react-test-renderer"

    在 package.json 里面加 script "test": "NODE_ENV=test jest"

    在跟路徑下加 jest.config.js

    module.exports = {
      setupFiles: ['<rootDir>/jest.setup.js'],
      testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/']
    }

    在跟路徑下加 jest.setup.js

    import { configure } from 'enzyme'
    import Adapter from 'enzyme-adapter-react-16'
    
    configure({
      adapter: new Adapter()
    })

    接下來就可以愉快的寫測試了

    e2e 測試

    跳過了~~~

    angular 環境

    之所以加了這一節,還是因為多少寫過一些 angular,angular 作為框架本身就是全面的,cli 新建的項目自身就帶有 unit 測試和 e2e 測試

    unit 測試默認是karma +jasmine e2e 測試默認是protractor

    也沒什么可爭辯的,這就是官方解決方案,用起來也方便順手

    總結

    聊了好多個環境,其實行文目的主要有兩方面

    • 測試思想,如何寫好單元測試,主要集中在前半文
    • 測試工具推薦和相應配置

    測試本身并不復雜,但是想寫出高效測試并不容易,千萬不要形成為了測試而測試的想法

    用謊言去驗證謊言得到的還是謊言。。。

    大多數情況下都是項目在趕進度沒空寫測試,抽空把測試補上真的是一件值得去做的事情

    原文轉自:https://juejin.im/post/5b374d8c6fb9a00e2d480bfe

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>