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>

        前端如何做單元測試? 看這篇就入門了

        共 9477字,需瀏覽 19分鐘

         ·

        2022-05-15 19:28

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

        點擊上方?程序員成長指北,關(guān)注公眾號

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

        前言

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

        前端為什么需要單元測試?

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

        2. 正確性:測試可以驗證代碼的正確性,在上線前做到心里有底。

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

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

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

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

        常見單元測試工具

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

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

        Github stars & issues

        npm 下載量

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

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

        框架對比

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

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

        ./sum.js

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

        module.exports?=?sum;

        Mocha + Chai 方式

        Mocha 需要引入 chai 或則其他斷言庫去斷言, 如果你需要查看覆蓋率報告你還需要安裝 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(1,?2)?===?3);
        ??});
        });

        Jest 方式

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

        ./test/sum.test.js

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

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

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

        如何開始?

        1.安裝依賴

        npm?install?--save-dev?jest

        2.簡單的例子

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

        ./sum.js

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

        module.exports?=?sum;

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

        ./test/sum.test.js

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

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

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

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

        運行 npm run test ,jest 將打印下面這個消息

        3.不支持部分 ES6 語法

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

        如以下文件改寫成 ES6 寫法后,運行 npm run test將會報錯

        ./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(1,?2)).toBe(3);
        });

        報錯

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

        解決辦法

        安裝依賴

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

        根目錄加入.babelrc

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

        再次運行 npm run test ,問題解決

        原理

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

        4.測試 ts 文件

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

        安裝依賴

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

        **改寫 **.babelrc

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

        為了解決編輯器對 jest 斷言方法的類型報錯,如 test、expect 的報錯,你還需要安裝

        npm?install?--save-dev?@types/jest

        ./get.ts

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

        export?function?get<T>(object:?any,?path:?Array,?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('測試嵌套對象存在的可枚舉屬性?line1',?()?=>?{
        ??expect(get({
        ????id:?101,
        ????email:?'[email protected]',
        ????personalInfo:?{
        ??????name:?'Jack',
        ??????address:?{
        ????????line1:?'westwish?st',
        ????????line2:?'washmasher',
        ????????city:?'wallas',
        ????????state:?'WX'
        ??????}
        ????}
        ??},?['personalInfo',?'address',?'line1'])).toBe('westwish?st');
        });

        運行 npm run test

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

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

        改寫 package.json

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

        效果

        5.生成測試覆蓋率報告

        什么是單元測試覆蓋率?

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

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

        如何生成?

        加入?jest.config.js ?文件

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

        再次運行效果

        參數(shù)解讀

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

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

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

        jest.config.js ?文件

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

        上述閥值要求我們的測試用例足夠充分,如果我們的用例沒有足夠充分,則下面的報錯將會幫助你去完善

        6.如何編寫單元測試

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

        編寫 fetchEnv 方法

        ./src/utils/fetchEnv.ts ?文件

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

        ?enum?IEnvEnum?{
        ??DEV?=?'dev',?//?開發(fā)
        ??TEST?=?'test',?//?測試
        ??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;
        }

        編寫對應(yīng)的單元測試

        ./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 修飾符允許你測試結(jié)果不等于某個值的情況

        ./test/sum.test.js

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

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

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

        ./src/utils/userInfo.js

        export?const?getUserInfo?=?()?=>?{
        ??return?{
        ????name:?'moji',
        ????age:?24,
        ??}
        }

        ./test/userInfo.test.js

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

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

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

        .toHaveLength 可以很方便的用來測試字符串和數(shù)組類型的長度是否滿足預(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ù)組長度應(yīng)該為3',?()?=>?{
        ??expect(getIntArray(3)).toHaveLength(3);
        })

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

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

        ./test/getIntArray.test.js

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

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

        .toMatch 傳入一個正則表達(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);
        })

        測試異步函數(shù)

        ./servers/fetchUser.js

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

        export?const?fetchUser?=?()?=>?{
        ??return?new?Promise((resole)?=>?{
        ????setTimeout(()?=>?{
        ??????resole({
        ????????name:?'moji',
        ????????age:?24,
        ??????})
        ????},?2000)
        ??})
        }

        ./test/fetchUser.test.js

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

        test('fetchUser()?可以請求到一個用戶名字為?moji',?async?()?=>?{
        ??const?data?=??await?fetchUser();

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

        這里你可能看到這樣一條報錯

        這是因為?@babel/preset-env?支持 async await 導(dǎo)致的,這時候就需要對 babel 配置進(jìn)行增強,可以安裝 @babel/plugin-transform-runtime 這個插件解決

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

        同時改寫 .babelrc

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

        再次運行就不會出現(xiàn)報錯了

        .toContain 匹配對象中是否包含

        ./test/toContain.test.js

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

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

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

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

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

        總結(jié)

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

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

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

        參考文獻(xiàn)

        淺談前端單元測試 (https://juejin.cn/post/6844903624301084680)

        Jest 官方文檔 (https://www.jestjs.cn/docs/getting-started)

        Node 社群



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



        如果你覺得這篇內(nèi)容對你有幫助,我想請你幫我2個小忙:

        1. 點個「在看」,讓更多人也能看到這篇文章
        2. 訂閱官方博客?www.inode.club?讓我們一起成長

        點贊和在看就是最大的支持??

        瀏覽 53
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        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精品高清一区二区三区 | 国产成人无码AA片免费看 | 一级国产国产一级 | 91久久国产精品视频 | 大乳少妇p00ps巨大吃奶 | 美女骚逼久久久久久久久久久久 | 周妍希裸体视频 | 欧美日韩在线视频免费播放 | 免费没广告直接看成人AV |