1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        估計(jì)很多前端都沒學(xué)過單元測(cè)試~

        共 15412字,需瀏覽 31分鐘

         ·

        2022-01-01 21:06


        大廠技術(shù)  高級(jí)前端  Node進(jìn)階

        點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

        回復(fù)1,加入高級(jí)Node交流群



        前言

        對(duì)于現(xiàn)在的前端工程,一個(gè)標(biāo)準(zhǔn)完整的項(xiàng)目,通常情況單元測(cè)試是非常必要的。但很多時(shí)候我們只是完成了項(xiàng)目而忽略了項(xiàng)目測(cè)試。我認(rèn)為其中一個(gè)很大的原因是很多人對(duì)單元測(cè)試認(rèn)知不夠,因此我寫了這邊文章,一方面期望通過這篇文章讓你對(duì)單元測(cè)試有一個(gè)初步認(rèn)識(shí)。另一個(gè)方面希望通過代碼示例,讓你掌握寫單元測(cè)試實(shí)踐能力。

        前端為什么需要單元測(cè)試?

        1. 必要性:JavaScript 缺少類型檢查,編譯期間無法定位到錯(cuò)誤,單元測(cè)試可以幫助你測(cè)試多種異常情況。

        2. 正確性:測(cè)試可以驗(yàn)證代碼的正確性,在上線前做到心里有底。

        3. 自動(dòng)化:通過 console 雖然可以打印出內(nèi)部信息,但是這是一次性的事情,下次測(cè)試還需要從頭來過,效率不能得到保證。通過編寫測(cè)試用例,可以做到一次編寫,多次運(yùn)行。

        4. 保證重構(gòu):互聯(lián)網(wǎng)行業(yè)產(chǎn)品迭代速度很快,迭代后必然存在代碼重構(gòu)的過程,那怎么才能保證重構(gòu)后代碼的質(zhì)量呢?有測(cè)試用例做后盾,就可以大膽的進(jìn)行重構(gòu)。

        現(xiàn)狀

        下面是一份抽樣調(diào)查片段,抽樣依據(jù)如下:

        • 向 200 名相關(guān)者發(fā)出在線問卷調(diào)查,其中 70 人回答了問卷中的問題,前端人數(shù)占 81.16%,如果你有興趣的話,也可以幫我填一下調(diào)查問卷 (https://www.wjx.cn/vm/Ombu9q1.aspx)

        • 數(shù)據(jù)收集日期:2021.09.21—2021.10.08

        • 目標(biāo)群體:所有開發(fā)人員

        • 組織規(guī)模:不到 50 人,50 到 100人, 100人以上

        你執(zhí)行過 JavaScript 單元測(cè)試嗎?

        調(diào)查中的另一個(gè)有趣的見解是,在大型組織中單元測(cè)試更受歡迎。其中一個(gè)原因可能是,由于大型組織需要處理大規(guī)模的產(chǎn)品,以及頻繁的功能迭代吧。這種持續(xù)的迭代方式,迫使他們進(jìn)行自動(dòng)化測(cè)試的投入。更具體地說,單元測(cè)試有助于增強(qiáng)產(chǎn)品的整體質(zhì)量。

        另外,報(bào)告顯示超 80% 人認(rèn)為單元測(cè)試可以有效的提高質(zhì)量,超 60% 人使用過 Jest 去編寫前端單元測(cè)試,超 40% 的人認(rèn)為單元測(cè)試覆蓋率是重要的且覆蓋率應(yīng)該大于 80%。

        常見單元測(cè)試工具

        目前用的最多的前端單元測(cè)試框架主要有 Mocha (https://mochajs.cn/)、Jest (https://www.jestjs.cn/),但我推薦你使用 Jest,因?yàn)?Jest 和 Mocha 相比,無論從 github starts & issues 量,npm下載量相比,都有明顯優(yōu)勢(shì)。

        github stars 以及 npm 下載量的實(shí)時(shí)數(shù)據(jù),參見:jest vs mocha (https://www.npmtrends.com/jest-vs-mocha) 截圖日期為 2021.11.25

        Github stars & issues

        npm 下載量

        Jest 的下載量較大,一部分原因是因?yàn)?create-react-app 腳手架默認(rèn)內(nèi)置了 Jest, 而大部分 react 項(xiàng)目都是用它生成的。

        從 github starts & issues 以及 npm 下載量角度來看,Jest 的關(guān)注度更高,社區(qū)也更活躍

        框架對(duì)比

        框架 斷言 異步 代碼覆蓋率
        Mocha 不支持(需要其他庫(kù)支持) 友好 不支持(需要其他庫(kù)支持)
        Jest 默認(rèn)支持 友好 支持
        • Mocha 生態(tài)好,但是需要較多的配置來實(shí)現(xiàn)高擴(kuò)展性
        • Jest 開箱即用

        比如對(duì) sum 函數(shù)寫用例

        ./sum.js

        function sum(a, b{
          return a + b;
        }

        module.exports = sum;

        Mocha + Chai 方式

        Mocha 需要引入 chai 或則其他斷言庫(kù)去斷言, 如果你需要查看覆蓋率報(bào)告你還需要安裝 nyc 或者其他覆蓋率工具

        ./test/sum.test.js

        const { expect, assert } = require('chai');
        const sum = require('../sum');

        describe('sum'function({
          it('adds 1 + 2 to equal 3', () => {
            assert(sum(12) === 3);
          });
        });

        Jest 方式

        Jest 默認(rèn)支持?jǐn)嘌?,同時(shí)默認(rèn)支持覆蓋率測(cè)試

        ./test/sum.test.js

        const sum = require('./sum');

        describe('sum function test', () => {
          it('sum(1, 2) === 3', () => {
            expect(sum(12)).toBe(3);
          });
          
          // 這里 test 和 it 沒有明顯區(qū)別,it 是指: it should xxx, test 是指 test xxx
          test('sum(1, 2) === 3', () => {
            expect(sum(12)).toBe(3);
          });
        })

        可見無論是受歡迎度和寫法上,Jest 都有很大的優(yōu)勢(shì),因此推薦你使用開箱即用的 Jest

        如何開始?

        1.安裝依賴

        npm install --save-dev jest

        2.簡(jiǎn)單的例子

        首先,創(chuàng)建一個(gè) sum.js 文件

        ./sum.js

        function sum(a, b{
          return a + b;
        }

        module.exports = sum;

        創(chuàng)建一個(gè)名為 sum.test.js 的文件,這個(gè)文件包含了實(shí)際測(cè)試內(nèi)容:

        ./test/sum.test.js

        const sum = require('../sum');

        test('adds 1 + 2 to equal 3', () => {
          expect(sum(12)).toBe(3);
        });

        將下面的配置部分添加到你的 package.json 里面

        {
          "scripts": {
            "test""jest"
          },
        }

        運(yùn)行 npm run test ,jest 將打印下面這個(gè)消息

        3.不支持部分 ES6 語(yǔ)法

        nodejs 采用的是 CommonJS 的模塊化規(guī)范,使用 require 引入模塊;而 import 是 ES6 的模塊化規(guī)范關(guān)鍵字。想要使用 import,必須引入 babel 轉(zhuǎn)義支持,通過 babel 進(jìn)行編譯,使其變成 node 的模塊化代碼

        如以下文件改寫成 ES6 寫法后,運(yùn)行 npm run test將會(huì)報(bào)錯(cuò)

        ./sum.js

        export function sum(a, b{
          return a + b;
        }

        ./test/sum.test.js

        import { sum } from '../sum';

        test('adds 1 + 2 to equal 3', () => {
          expect(sum(12)).toBe(3);
        });

        報(bào)錯(cuò)

        為了能使用這些新特性,我們就需要使用 babel 把 ES6 轉(zhuǎn)成 ES5 語(yǔ)法

        解決辦法

        安裝依賴

        npm install --save-dev @babel/core @babel/preset-env

        根目錄加入.babelrc

        {   "presets": ["@babel/preset-env"] }

        再次運(yùn)行 npm run test ,問題解決

        原理

        jest 運(yùn)行時(shí)內(nèi)部先執(zhí)行( jest-babel ),檢測(cè)是否安裝 babel-core,然后取 .babelrc 中的配置運(yùn)行測(cè)試之前結(jié)合 babel 先把測(cè)試用例代碼轉(zhuǎn)換一遍然后再進(jìn)行測(cè)試

        4.測(cè)試 ts 文件

        jest 需要借助 .babelrc 去解析 TypeScript 文件再進(jìn)行測(cè)試

        安裝依賴

        npm install --save-dev @babel/preset-typescript

        **改寫 **.babelrc

        {   "presets": ["@babel/preset-env""@babel/preset-typescript"] }

        為了解決編輯器對(duì) jest 斷言方法的類型報(bào)錯(cuò),如 test、expect 的報(bào)錯(cuò),你還需要安裝

        npm install --save-dev @types/jest

        ./get.ts

        /**
         * 訪問嵌套對(duì)象,避免代碼中出現(xiàn)類似 user && user.personalInfo ? user.personalInfo.name : null 的代碼
         */

        export function get<T>(object: any, path: Array<number | string>, defaultValue?: T) : T {
          const result = path.reduce((obj, key) => obj !== undefined ? obj[key] : undefined, object);

          return result !== undefined ? result : defaultValue;
        }

        ./test/get.test.ts

        import { get } from './get';

        test('測(cè)試嵌套對(duì)象存在的可枚舉屬性 line1', () => {
          expect(get({
            id101,
            email'[email protected]',
            personalInfo: {
              name'Jack',
              address: {
                line1'westwish st',
                line2'washmasher',
                city'wallas',
                state'WX'
              }
            }
          }, ['personalInfo''address''line1'])).toBe('westwish st');
        });

        運(yùn)行 npm run test

        5.持續(xù)監(jiān)聽

        為了提高效率,可以通過加啟動(dòng)參數(shù)的方式讓 jest 持續(xù)監(jiān)聽文件的修改,而不需要每次修改完再重新執(zhí)行測(cè)試用例

        改寫 package.json

        "scripts": {     "test""jest --watchAll"   },

        效果

        5.生成測(cè)試覆蓋率報(bào)告

        什么是單元測(cè)試覆蓋率?

        單元測(cè)試覆蓋率是一種軟件測(cè)試的度量指標(biāo),指在所有功能代碼中,完成了單元測(cè)試的代碼所占的比例。有很多自動(dòng)化測(cè)試框架工具可以提供這一統(tǒng)計(jì)數(shù)據(jù),其中最基礎(chǔ)的計(jì)算方式為:

        單元測(cè)試覆蓋率 = 被測(cè)代碼行數(shù) / 參測(cè)代碼總行數(shù) * 100%

        如何生成?

        加入 jest.config.js  文件

        module.exports = {
          // 是否顯示覆蓋率報(bào)告
          collectCoveragetrue,
          // 告訴 jest 哪些文件需要經(jīng)過單元測(cè)試測(cè)試
          collectCoverageFrom: ['get.ts''sum.ts''src/utils/**/*'],
        }

        再次運(yùn)行效果

        參數(shù)解讀

        參數(shù)名 含義 說明
        % stmts 語(yǔ)句覆蓋率 是不是每個(gè)語(yǔ)句都執(zhí)行了?
        % Branch 分支覆蓋率 是不是每個(gè) if 代碼塊都執(zhí)行了?
        % Funcs 函數(shù)覆蓋率 是不是每個(gè)函數(shù)都調(diào)用了?
        % Lines 行覆蓋率 是不是每一行都執(zhí)行了?

        設(shè)置單元測(cè)試覆蓋率閥值

        個(gè)人認(rèn)為既然在項(xiàng)目中集成了單元測(cè)試,那么非常有必要關(guān)注單元測(cè)試的質(zhì)量,而覆蓋率則一定程度上客觀的反映了單測(cè)的質(zhì)量,同時(shí)我們還可以通過設(shè)置單元測(cè)試閥值的方式提示用戶是否達(dá)到了預(yù)期質(zhì)量。

        jest.config.js  文件

        module.exports = {
          collectCoveragetrue// 是否顯示覆蓋率報(bào)告
          collectCoverageFrom: ['get.ts''sum.ts''src/utils/**/*'], // 告訴 jest 哪些文件需要經(jīng)過單元測(cè)試測(cè)試
          coverageThreshold: {
            global: {
              statements90// 保證每個(gè)語(yǔ)句都執(zhí)行了
              functions90// 保證每個(gè)函數(shù)都調(diào)用了
              branches90// 保證每個(gè) if 等分支代碼都執(zhí)行了
            },
          },

        上述閥值要求我們的測(cè)試用例足夠充分,如果我們的用例沒有足夠充分,則下面的報(bào)錯(cuò)將會(huì)幫助你去完善

        6.如何編寫單元測(cè)試

        下面我們以 fetchEnv 方法作為案例,編寫一套完整的單元測(cè)試用例供讀者參考

        編寫 fetchEnv 方法

        ./src/utils/fetchEnv.ts  文件

        /**
         * 環(huán)境參數(shù)枚舉
         */

         enum IEnvEnum {
          DEV = 'dev'// 開發(fā)
          TEST = 'test'// 測(cè)試
          PRE = 'pre'// 預(yù)發(fā)
          PROD = 'prod'// 生產(chǎn)
        }

        /**
         * 根據(jù)鏈接獲取當(dāng)前環(huán)境參數(shù)
         * @param {string?} url 資源鏈接
         * @returns {IEnvEnum} 環(huán)境參數(shù)
         */

        export function fetchEnv(url: string): IEnvEnum {
          const envs = [IEnvEnum.DEV, IEnvEnum.TEST, IEnvEnum.PRE];

          return envs.find((env) => url.includes(env)) || IEnvEnum.PROD;
        }

        編寫對(duì)應(yīng)的單元測(cè)試

        ./test/fetchEnv.test.ts  文件

        import { fetchEnv } from '../src/utils/fetchEnv';

        describe('fetchEnv', () => {
          it ('判斷是否 dev 環(huán)境', () => {
            expect(fetchEnv('https://www.imooc.dev.com/')).toBe('dev');
          });

          it ('判斷是否 test 環(huán)境', () => {
            expect(fetchEnv('https://www.imooc.test.com/')).toBe('test');
          });

          it ('判斷是否 pre 環(huán)境', () => {
            expect(fetchEnv('https://www.imooc.pre.com/')).toBe('pre');
          });

          it ('判斷是否 prod 環(huán)境', () => {
            expect(fetchEnv('https://www.imooc.prod.com/')).toBe('prod');
          });

          it ('判斷是否 prod 環(huán)境', () => {
            expect(fetchEnv('https://www.imooc.com/')).toBe('prod');
          });
        });

        執(zhí)行結(jié)果

        7.常用斷言方法

        關(guān)于斷言方法有很多,這里僅摘出常用方法,如果你想了解更多,你可以去 Jest 官網(wǎng) API (https://www.jestjs.cn/docs/expect) 部分查看

        .not 修飾符允許你測(cè)試結(jié)果不等于某個(gè)值的情況

        ./test/sum.test.js

        import { sum } from './sum';

        test('sum(2, 4) 不等于 5', () => {
          expect(sum(24)).not.toBe(5);
        })

        .toEqual 匹配器會(huì)遞歸的檢查對(duì)象所有屬性和屬性值是否相等,常用來檢測(cè)引用類型

        ./src/utils/userInfo.js

        export const getUserInfo = () => {
          return {
            name'moji',
            age24,
          }
        }

        ./test/userInfo.test.js

        import { getUserInfo }  from '../src/userInfo.js';

        test('getUserInfo()返回的對(duì)象深度相等', () => {
          expect(getUserInfo()).toEqual(getUserInfo());
        })

        test('getUserInfo()返回的對(duì)象內(nèi)存地址不同', () => {
          expect(getUserInfo()).not.toBe(getUserInfo());
        })

        .toHaveLength 可以很方便的用來測(cè)試字符串和數(shù)組類型的長(zhǎng)度是否滿足預(yù)期

        ./src/utils/getIntArray.js

        export const getIntArray = (num) => {
          if (!Number.isInteger(num)) {
            throw Error('"getIntArray"只接受整數(shù)類型的參數(shù)');
          }
          
          return [...new Array(num).keys()];
        };

        ./test/getIntArray.test.js

        ./test/getIntArray.test.js
        import { getIntArray }  from '../src/utils/getIntArray';

        test('getIntArray(3)返回的數(shù)組長(zhǎng)度應(yīng)該為3', () => {
          expect(getIntArray(3)).toHaveLength(3);
        })

        .toThorw 能夠讓我們測(cè)試被測(cè)試方法是否按照預(yù)期拋出異常

        但是需要注意的是:我們必須使用一個(gè)函數(shù)將被測(cè)試的函數(shù)做一個(gè)包裝,正如下面 getIntArrayWrapFn 所做的那樣,否則會(huì)因?yàn)楹瘮?shù)拋出錯(cuò)誤導(dǎo)致該斷言失敗。

        ./test/getIntArray.test.js

        import { getIntArray }  from '../src/utils/getIntArray';

        test('getIntArray(3.3)應(yīng)該拋出錯(cuò)誤', () => {
          function getIntArrayWrapFn({
            getIntArray(3.3);
          }
          
          expect(getIntArrayWrapFn).toThrow('"getIntArray"只接受整數(shù)類型的參數(shù)');
        })

        .toMatch 傳入一個(gè)正則表達(dá)式,它允許我們來進(jìn)行字符串類型的正則匹配

        ./test/userInfo.test.js

        import { getUserInfo }  from '../src/utils/userInfo.js';

        test("getUserInfo().name 應(yīng)該包含'mo'", () => {
          expect(getUserInfo().name).toMatch(/mo/i);
        })

        測(cè)試異步函數(shù)

        ./servers/fetchUser.js

        /** 
         * 獲取用戶信息
        */

        export const fetchUser = () => {
          return new Promise((resole) => {
            setTimeout(() => {
              resole({
                name'moji',
                age24,
              })
            }, 2000)
          })
        }

        ./test/fetchUser.test.js

        import { fetchUser } from '../src/fetchUser';

        test('fetchUser() 可以請(qǐng)求到一個(gè)用戶名字為 moji'async () => {
          const data =  await fetchUser();

          expect(data.name).toBe('moji')
        })

        這里你可能看到這樣一條報(bào)錯(cuò)

        這是因?yàn)?nbsp;@babel/preset-env 支持 async await 導(dǎo)致的,這時(shí)候就需要對(duì) babel 配置進(jìn)行增強(qiáng),可以安裝 @babel/plugin-transform-runtime 這個(gè)插件解決

        npm install --save-dev @babel/plugin-transform-runtime

        同時(shí)改寫 .babelrc

        {
          "presets": ["@babel/preset-env""@babel/preset-typescript"],
          "plugins": ["@babel/plugin-transform-runtime"]
        }

        再次運(yùn)行就不會(huì)出現(xiàn)報(bào)錯(cuò)了

        .toContain 匹配對(duì)象中是否包含

        ./test/toContain.test.js

        const names = ['liam''jim''bart'];

        test('匹配對(duì)象是否包含', () => {
          expect(names).toContain('jim');
        })

        檢查一些特殊的值(null,undefined 和 boolean)

        toBeNull 僅匹配 null
        toBeUndefined 僅匹配 undefined
        toBeDefined 與…相反 toBeUndefined
        toBeTruthy 匹配 if 語(yǔ)句視為 true 的任何內(nèi)容
        toBeFalsy 匹配 if 語(yǔ)句視為 false 的任何內(nèi)容

        檢查數(shù)字類型(number)
        toBeGreaterThan 大于
        toBeGreaterThanOrEqual 至少(大于等于)
        toBeLessThan 小于
        toBeLessThanOrEqual 最多(小于等于)
        toBeCloseTo 用來匹配浮點(diǎn)數(shù)(帶小數(shù)點(diǎn)的相等)

        總結(jié)

        以上就是文章全部?jī)?nèi)容,相信你閱讀完這篇文章后,已經(jīng)掌握了前端單元測(cè)試的基本知識(shí),甚至可以按照文章教學(xué)步驟,現(xiàn)在就可以在你的項(xiàng)目中接入單元測(cè)試。同時(shí)在閱讀過程中如果你有任何問題,或者有更好見解,更好的框架推薦,歡迎你在評(píng)論區(qū)留言!

        也許在你閱讀這篇文章之前,你本身就已掌握前端單元測(cè)試技能了,甚至已經(jīng)是這個(gè)領(lǐng)域的大牛了,那么首先我感到非常榮幸,同時(shí)也誠(chéng)懇的邀請(qǐng)你在評(píng)論區(qū)提出寶貴意見,我在這里提前說聲謝謝!

        最后感謝你在百忙之中抽出時(shí)間閱讀這篇文章,送人玫瑰,手有余香,如果你覺得文章對(duì)你有所幫助,希望可以幫我點(diǎn)個(gè)贊!

        Node 社群


        我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


           “分享、點(diǎn)贊、在看” 支持一波??

        瀏覽 41
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            好好的日视频 | 91久久人澡人妻人人做人精品 | 夫妻做爱视频免费毛片 | 精品无码一区二区三区爱奴 | 好爽~~~嗯~~~再快点 | 亚洲AV成人片在线观看 | 欧美性爱在线一区 | xxxx18hd亚洲hd护士 | 操屄电影网站 | 人人草人人爱 |