如何為我的VUE項目編寫高效的單元測試--Jest


Unit Testing(單元測試)--Jest

一個完整的測試程序通常由幾種不同的測試組合而成,比如end to end(E2E)測試,有時還包括整體測試、簡要測試和單元測試。這里要介紹的是Vue中的單元測試,使用流行的Jest JavaScript測試庫來運行我們的測試。

1.測試的目的

  • 排除故障

    每個應用的開發中,多少會出現一些意料之外的bug。通過測試應用程序,可以幫助我們大大減少此類問題,並增強應用程序的邏輯性。

  • 保證團隊成員的邏輯統一

    如果您是團隊的新成員,並且對應用程序還不熟悉,那么一組測試就好像是有經驗的開發人員監視你編寫代碼,確保您處於代碼應該執行的正確路線之內。通過這些測試,您可以確信在添加新功能或更改現有代碼時不會破壞任何東西。

  • 可以提高質量代碼

    當您在編寫vue組件時,由於考慮到測試,最好的方案將是創建獨立的、更可重用的組件。如果您開始為您的組件編寫測試,並且您注意到這些組件不容易測試,那么您可能會重構您的組件,最終起到改進它們的效果。

  • 起到很好的說明文檔作用

    正如在第一點中提到的,測試的另一個結果是,它最終可以為您的開發團隊生成良好的文檔。當某人對代碼庫還不熟悉時,他們可以查看測試以獲得指導,這可以提供關於組件應該如何工作的意圖的洞察,並為可能要測試的邊緣部分提供線索。

1.我們需要測試什么

在測試的過程中,很容易照成過分的測試一些不需要的東西,這會不必要地減慢開發時間。那么我們在Vue.js應用程序中測試什么呢?答案其實很簡單:組件。由於Vue應用程序是一個個組件相互組合而成的,我們需要測試它們各自的行為,以確保它們正常工作。

組件原理及分解

我們可以先分解組件,看看組件之間是怎么協調工作的。

一般來說,組件需要遵照我們定義的邏輯來工作,這里有一個輸入和輸出的概念。組件會接收一些靜態或動態的輸入值,然后依照邏輯輸出一些值或者是dom元素。

正常來說,組件的輸入輸出有以下這些:

 1// 輸入值
2
3// 靜態接收數據
4Component Data
5// 動態接收數據
6Component Props
7// 用戶互交,例如:一個用戶單擊的按鈕
8User Interaction 
9// 生命周期邏輯,例如:mounted(), created()
10Lifecycle Methods
11// 組件狀態值
12Vuex Store
13// 路由參數
14Route Params
15
16// 輸出值
17
18// 輸出的dom
19What is rendered to the DOM
20// 外部調用的函數
21External function calls
22// 組件觸發的事件
23Events emitted by the component
24// 路由的變更
25Route Changes
26// 組件狀態值得更新
27Updates to the Vuex Store
28// 對子組件的改變
29Connection with children
30

我們可以識別組件的輸入和輸出,挑選我們應該測試的內容,從而避開測試組件的內部業務邏輯。換句話說,我們不應該因為擔心每一行代碼如何工作而陷入困境。

這看起來可能有悖常理,但是單元測試的目標純粹是確保組件產生預期的結果。我們在這里不關心它是如何得出這個結果的。后期我們甚至可能會改變我們的邏輯方式,所以我們不希望測試如何實現這些邏輯。這些邏輯不是測試的工作。就單元測試而言,我們只要確保組件的輸出正常就行了。

要測試的部分

 1<template>
2    <div>
3        <button v-show="loggedIn">Logout</button>
4    </div>
5</template>
6
7<script>
8export default {
9    data() {
10        return {
11            loggedInfalse
12        }
13    }
14}
15
</script>

在這個例子中,我們有一個組件,如果loggedIn屬性為true,它將顯示一個logout按鈕。為了弄清楚我們應該測試這個組件的哪個部分,我們的第一步是確定組件的輸入和輸出。

 1input
2
3// 靜態數據
4data => loggedIn
5// 這個數據屬性決定按鈕是否顯示,所以這是一個我們應該測試的輸入
6
7output
8
9Dom輸出(button)
10根據輸入(loggedIn),我們的按鈕應該在什么時候顯示在DOM中

對於更復雜的組件,將有更多的方面需要測試,但同樣的方法也適用。

不測試的部分

對你應該測試的東西知道大概當然是必要的,知道你不應該測試的東西也是有幫助的。如下:

  • 不測試組件邏輯的詳情(implementation details)

    當單元測試時,我們不需要為某些邏輯是如何工作而煩惱,只要它們確實工作就行了。我們不在乎這里的內部結構。我們只關心組件產生了我們期望的輸出。

  • 不要測試框架本身

    開發人員經常試圖測試太多,包括框架本身的內部工作。但是框架作者已經建立了這樣的測試。

    例如,props中的數據類型,如果我們嘗試傳入錯誤數據,Vue將拋出錯誤。我們不需要浪費時間測試Vue.js框架。這包括不要對Vue路由器和Vuex進行不必要的測試。

  • 不測試第三方庫

    如果您使用的第三方庫是高質量的,那么它們已經有了自己的測試。我們不需要測試它們的內部結構。例如,我們不需要測試Axios是如何工作的。Axios隊為我們做了這件事。

2.編寫單元測試

當我們使用Vue CLI創建應用項目時:

  1. 首先需要選擇Manually select features自定義一些項目配置
  2. 然后在選擇項目依賴時,勾選Unit Testing選項
  3. 最后在選擇測試方案時。選擇Jest
  • vue/cli項目可以這樣安裝
1vue add @vue/unit-jest
2
3✔  Successfully installed plugin: @vue/cli-plugin-unit-jest
4// 如果安裝的jest運行報錯可能是版本問題,可以嘗試npm update

這樣創建的項目中就為我們安裝好了依賴。

打開項目后,讓我們從查看package.json開始,在這里我們將看到為我們安裝了Jest和vue測試實用程序。

1// package.json
2"devDependencies": {
3    "@vue/cli-plugin-unit-jest""^3.11.0",
4    "@vue/test-utils""1.0.0-beta.29"
5}

Jest是一個JavaScript測試框架,它專注於簡化單元測試。Jest將為我們運行單元測試,並在測試通過或失敗時向我們報告。

API list:

 1afterAll(fn, timeout)
2afterEach(fn, timeout)
3beforeAll(fn, timeout)
4beforeEach(fn, timeout)
5describe(name, fn)
6describe.each(table)(name, fn, timeout)
7describe.only(name, fn)
8describe.only.each(table)(name, fn)
9describe.skip(name, fn)
10describe.skip.each(table)(name, fn)
11test(name, fn, timeout)
12test.each(table)(name, fn, timeout)
13test.only(name, fn, timeout)
14test.only.each(table)(name, fn)
15test.skip(name, fn)
16test.skip.each(table)(name, fn)
17test.todo(name)

@vue/test-utils是Vue.js的官方單元測試實用程序庫。它使我們能夠在測試中渲染組件,然后對這些渲染的組件執行各種操作。這使得我們可以測試組件的運行結果。

如何運行Jest測試,我們來查看package.json中的運行腳本

1"scripts": {
2    ...
3    "test:unit""vue-cli-service test:unit"
4}
5// 在我們編寫好測試代碼后只要運行:
6npm run test:unit
7// 或者在CLI中運行測試就可以運行測試代碼
8// 這個命令的意思就是查看tests/unit目錄,並運行目錄下名為[componentsName].spec.js的文件。

查看項目根目錄下的tests/unit文件夾,有一個初始化的Example.spec.js文件。spec是specification的縮寫,即詳述組件運行規則的js文件

  • ## demo1(測試一個方法)

創建函數文件demo1.js

1function sum(a, b{
2  return a + b;
3}
4module.exports = sum;

在tests/unit/中創建demo1.spec.js

 1// 導入要測試的文件
2const sum = require('./sum')
3
4// 使用test(name, fn, timeout)創建一個測試
5test('adds 1 + 2 to equal 3', () => {
6  expect(sum(12)).toBe(3)
7})
8
9// 其中,name是對測試的描述,測試結果會打印
10// fn是要執行的測試
11// expect()方法中描述的是測試的結果
12// toBe()是測試預期的值

運行這個單元測試

1npm run test:unit
2
3PASS  tests/unit/demo1.spec.js
4✓ adds 1 + 2 to equal 3 (5ms)
5// PASS表示測試通過,函數運行正常
6// 下面列出每一項text(),[test.name (測試使用的時間)]
  • ## demo2(測試一個組件)

創建組件demo2.vue

 1<template>
2    <div>
3        <button v-show="loggedIn">Logout</button>
4    </div>
5</template>
6<script>
7export default {
8    data() {
9        return {
10            loggedInfalse
11        }
12    }
13}
14
</script>

關於這個組件,前面我們已經介紹過了,要測試的點如下:

  1. 如果用戶未登錄,則不顯示“注銷”按鈕
  2. 如果用戶已登錄,則顯示“注銷”按鈕

在tests/unit/中創建demo2.spec.js

 1// 導入要測試的組件
2import demo2 from '@/components/demo2'
3
4// 因為我們要測試的是組件,所以我們在測試時需要加載這個組件
5// `@vue/test-utils`為我們提供了這項功能
6import { mount } from '@vue/test-utils'
7// 使用describe(name, fn)創建一個測試組
8// 當我們有多個測試時,用這種方式組織它們比較具有邏輯性
9
10describe('ComponentsDemo2', () => {
11
12    // 其中寫所有組件內需要的測試
13    test('if user is not logged in, do not show logout button', () => {
14        // 安裝組件
15        const wrapper = mount(AppHeader)
16
17        // 默認登錄狀態是false,則找到組件內的btn,查看其可見性為false
18        expect(wrapper.find('button').isVisible()).toBe(false)
19    })
20    test('if a user is logged in, show logout button', () => {
21        // 安裝組件
22        const wrapper = mount(AppHeader)
23
24        // 要測試登錄狀態是‘已登錄’,則先設置其狀態值
25        wrapper.setData({ loggedIn: true })
26
27        // 這時找到組件內的btn,查看其可見性為true
28        expect(wrapper.find('button').isVisible()).toBe(true)
29    })
30})

提示:

您可能還會看到使用it()的測試塊,它是test()的別名。

@vue/test-utils,還提供shallowMount()方法。如果組件有子組件,shallowMount()將返回該組件的本身,而不是完全渲染的詳細組件單元測試的焦點是單一的組件,多數時候我們會忽略其子組件。

運行這個單元測試

 1npm run test:unit
2
3PASS  tests/unit/demo2.spec.js
4ComponentsDemo2
5    ✓ if user is not logged in, do not show logout button (20ms)
6    ✓ if a user is logged inshow logout button (30ms)
7
8Test Suites: 1 passed, 1 total
9Tests:       2 passed, 2 total
10Snapshots:   0 total
11Time:        2.653s
12Ran all test suites.

步驟總結如下圖:

  1. 創建一個測試組:describe()
  2. 開始一個測試:test()
  3. 安裝渲染組件:mount()
  4. 必要的時候設置組件參數:setData()
  5. 調試預期的組件行為及數據正確與否:expect()

長按二維碼關注公眾號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM