<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何做前端單元測(cè)試

          共 9520字,需瀏覽 20分鐘

           ·

          2022-01-08 12:30

          ????這是第?125?篇不摻水的原創(chuàng),想要了解更多,請(qǐng)戳上方藍(lán)色字體:政采云前端團(tuán)隊(duì)?關(guān)注我們吧~

          本文首發(fā)于政采云前端團(tuán)隊(duì)博客:如何做前端單元測(cè)試

          https://www.zoo.team/article/unit-testing

          前言

          對(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不支持(需要其他庫支持)友好不支持(需要其他庫支持)
          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 或則其他斷言庫去斷言, 如果你需要查看覆蓋率報(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(1,?2)?===?3);
          ??});
          });

          Jest 方式

          Jest 默認(rèn)支持?jǐn)嘌裕瑫r(shí)默認(rèn)支持覆蓋率測(cè)試

          ./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)勢(shì),因此推薦你使用開箱即用的 Jest

          如何開始?

          1.安裝依賴

          npm?install?--save-dev?jest

          2.簡單的例子

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

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

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

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

          3.不支持部分 ES6 語法

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

          報(bào)錯(cuò)

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

          解決辦法

          安裝依賴

          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,?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({
          ????id:?101,
          ????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)告
          ??collectCoverage:?true,
          ??//?告訴?jest?哪些文件需要經(jīng)過單元測(cè)試測(cè)試
          ??collectCoverageFrom:?['get.ts',?'sum.ts',?'src/utils/**/*'],
          }

          再次運(yùn)行效果

          參數(shù)解讀

          參數(shù)名含義說明
          % stmts語句覆蓋率是不是每個(gè)語句都執(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?=?{
          ??collectCoverage:?true,?//?是否顯示覆蓋率報(bào)告
          ??collectCoverageFrom:?['get.ts',?'sum.ts',?'src/utils/**/*'],?//?告訴?jest?哪些文件需要經(jīng)過單元測(cè)試測(cè)試
          ??coverageThreshold:?{
          ????global:?{
          ??????statements:?90,?//?保證每個(gè)語句都執(zhí)行了
          ??????functions:?90,?//?保證每個(gè)函數(shù)都調(diào)用了
          ??????branches:?90,?//?保證每個(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(2,?4)).not.toBe(5);
          })

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

          ./src/utils/userInfo.js

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

          ./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ù)組類型的長度是否滿足預(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 能夠讓我們測(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',
          ????????age:?24,
          ??????})
          ????},?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)?@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?語句視為?true?的任何內(nèi)容
          toBeFalsy?匹配?if?語句視為?false?的任何內(nèi)容

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

          總結(jié)

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

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

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

          參考文獻(xiàn)

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

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

          看完兩件事

          如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我兩件小事

          1.點(diǎn)個(gè)「在看」,讓更多人也能看到這篇內(nèi)容(點(diǎn)了在看」,bug -1 ??

          2.關(guān)注公眾號(hào)「前端Sharing」,持續(xù)為你推送精選好文

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美成人一区二区 | 黑人操欧美人 | 操逼大战 | 青草视频亚洲 | 欧美一级AA大片免费看视频 |