<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>

          如何寫好eggjs單元測試

          共 10253字,需瀏覽 21分鐘

           ·

          2021-05-22 23:45

          點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號

          回復(fù)算法,加入前端編程面試算法每日一題群

          來源:bigo大魔王

          https://juejin.cn/post/6949084159801294855

          如何寫好eggjs單元測試

          前言

          筆者在平時(shí)面試前端同學(xué)時(shí),經(jīng)常遇到候選人有nodejs開發(fā)經(jīng)驗(yàn),但是很少有編寫單元測試。希望寫下這篇文章,讓大家多重視單元測試,交付高質(zhì)量的代碼。

          如果你的項(xiàng)目單元測試分支規(guī)范率達(dá)到80%以上,我就認(rèn)為這個(gè)同學(xué)的代碼質(zhì)量意識特別好。

          為什么要單元測試

          如測試金字塔,單元測試是底座。

          引用eggjs官網(wǎng)的話猛戳這里

          • 你的代碼質(zhì)量如何度量?
          • 你是如何保證代碼質(zhì)量?
          • 你敢隨時(shí)重構(gòu)代碼嗎?
          • 你是如何確保重構(gòu)的代碼依然保持正確性?
          • 你是否有足夠信心在沒有測試的情況下隨時(shí)發(fā)布你的代碼?

          如果答案都比較猶豫,那么就證明我們非常需要單元測試。

          特別是大型nodejs項(xiàng)目,經(jīng)過多年的代碼迭代,業(yè)務(wù)邏輯復(fù)雜,代碼改動很容易牽一發(fā)動全身,單元測試就能給應(yīng)用的穩(wěn)定性提供了一層保障。不用面對qa的靈魂拷問:為什么老是你的bug最多!

          image.png

          測試準(zhǔn)備

          eggjs提供了很好的測試模塊:egg-mock,通過egg-mock/bootstrap,可以快速實(shí)例化app

          // test/controller/home.test.js
          const { app, mock, assert } = require('egg-mock/bootstrap');

          describe('test/controller/home.test.js', () => {
            // test cases
          });
          復(fù)制代碼

          自定義mockServiceByData與getMockData

          但是我們知道,要寫單測,對mock數(shù)據(jù)比較依賴,需要我們準(zhǔn)備大量的json數(shù)據(jù),故在app.mockService基礎(chǔ)上拓展了mockServiceByData與getMockData方法

          1.新建test/global.ts

          注:如果是bigo內(nèi)網(wǎng),可以import bigoMock from '@bigo/bgegg-mock';

          import * as assert from 'assert';
          import { app } from 'egg-mock/bootstrap';
          import * as path from 'path';
          import * as fs from 'fs';

          class BigoMock {
            app;
            ctx;
            assert = assert; // 掛載assert
            async before() {
              console.log('hello bigoMock');
              this.app = app;
              await app.ready();
              this.ctx = app.mockContext();
              return;
            }
            /**
             * 模擬 Service 方法返回值
             * @param service 方法類
             * @param methodName 方法名
             * @param fileName 文件名(狀態(tài))
             */

            mockServiceByData(service, methodName, fileName) {
              let serviceClassName = '';
              if (typeof service === 'string') {
                const arr = service.split('.');
                serviceClassName = arr[arr.length - 1];
              }
              const servicePaths = path.join(serviceClassName, methodName);
              this.app.mockService(service, methodName, () => {
                return this.getMockData(servicePaths, fileName);
              });
            }
            /**
             * 獲取本地test/mockData的mock數(shù)據(jù)
             * @param folder 文件夾
             * @param fileName 文件名
             */

            getMockData(folder, fileName) {
              return this.getJson(folder, fileName);
            }
            /**
             * 約定從test/mockData/service/methodName/fileName.json獲取數(shù)據(jù)
             * @param folder 文件夾
             * @param fileName 文件名
             */

            getJson(folder, fileName) {
              // 默認(rèn)追加json后綴
              console.log(path.extname(fileName));
              if (!path.extname(fileName)) {
                fileName = fileName + '.json';
              }
              const fullPaths = path.join(process.cwd(), 'test/mockData', folder, fileName);
              return fs.readFileSync(fullPaths, 'utf-8');
            }
          }

          const bigoMock = new BigoMock();
          (async function({
            await bigoMock.before();
          })();

          export default bigoMock;
          復(fù)制代碼

          2.mockServiceByData

          import bigoMock from './../global';

          describe('user接口單測用例', () => {

            it('should mock fengmk1 exists', () => {
              // 返回test/mockData/user/get/success.json
              bigoMock.mockServiceByData('user''get''success.json');

              return app.httpRequest()
                .get('/user?name=fengmk1')
                .expect(200)
                // 返回了原本不存在的用戶信息
                .expect({
                  name'fengmk1',
                });
            });

          });
          復(fù)制代碼

          3.getMockData

          import bigoMock from './../global';

          // TESTS=test/app/service/spider/githubIssues/index.test.ts npm test
          describe('githubIssues爬蟲單測用例', () => {

            it('解析html結(jié)構(gòu)成功'async () => {
              // 返回test/mockData/githubIssues/html_mock.js
              const html = bigoMock.getMockData('githubIssues''html_mock.js');

              const result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);
              bigoMock.assert(result[0].title === 'nginx反向代理實(shí)現(xiàn)線上調(diào)試');
              bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
            });

          });
          復(fù)制代碼

          編寫Service單測

          如果編寫controller單測,從用戶請求到達(dá) ==》 返回ctx.body數(shù)據(jù),這個(gè)過程會涉及Controller、Service、以及下游接口調(diào)用等環(huán)節(jié)。經(jīng)過的分支邏輯太多,數(shù)據(jù)會有很多中間狀態(tài),這樣要準(zhǔn)備的單測用例就特別復(fù)雜,導(dǎo)致單測分支覆蓋率低。但是Service就不一樣了,每個(gè)Service函數(shù)都是單一功能,有明確的輸入、輸出結(jié)果,只要我們的service單元測試代碼足夠多,單測覆蓋率自然就上去了。

          當(dāng)然應(yīng)用的 Controller、Helper、Extend 等代碼,都必須也有對應(yīng)的單元測試保證代碼質(zhì)量。

          綜上,本文會重點(diǎn)講service單測。

          如何執(zhí)行單個(gè)測試文件

          我們知道執(zhí)行 npm run test (實(shí)際執(zhí)行 egg-bin test),就會跑全部的測試用例,但是我們通常編寫單測時(shí),只關(guān)心當(dāng)前單測的執(zhí)行情況。我們可以在命令行執(zhí)行如下命令,執(zhí)行指定測試文件

          TESTS=test/app/service/spider/githubIssues/index.test.ts npm test
          復(fù)制代碼

          如果我們一個(gè)單測文件的測試用例很多,只希望跑一個(gè)用例,可以使用it.only

          import bigoMock from './../../../../global';

          // TESTS=test/app/service/spider/githubIssues/index.test.ts npm test
          describe('githubIssues爬蟲單測用例', () => {

            it('解析html結(jié)構(gòu)成功'async () => {
              const html = bigoMock.getMockData('githubIssues''html_mock.js');
              const result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);
              bigoMock.assert(result[0].title === 'nginx反向代理實(shí)現(xiàn)線上調(diào)試');
              bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
            });

            // 只會執(zhí)行該用例
            it.only('解析html結(jié)構(gòu)失敗'async () => {
              const html = '';
              const result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);
              bigoMock.assert(result.length === 0);
            });

          });
          復(fù)制代碼

          注:在提交代碼前,記得移除only,否則執(zhí)行npm run test時(shí),只會執(zhí)行該用例??

          mock輸入

          1.常量mock

          一個(gè)service方法,通常有多個(gè)arguments,我們在調(diào)用service時(shí),可以簡單構(gòu)造入?yún)?/p>

          // 只會執(zhí)行該用例
          it.only('解析html結(jié)構(gòu)失敗'async () => {
            const html = ''// 常量mock
            const result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);
            bigoMock.assert(result.length === 0);
          });
          復(fù)制代碼

          2.文件mock

          如果入?yún)ο筝^復(fù)雜,或者其他單測文件也可以復(fù)用,那么使用文件mock比較方便

          it('解析html結(jié)構(gòu)成功'async () => {
            const html = bigoMock.getMockData('githubIssues''html_mock.js'); // 文件mock
            const result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);
            bigoMock.assert(result[0].title === 'nginx反向代理實(shí)現(xiàn)線上調(diào)試');
            bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
          });
          復(fù)制代碼

          3.service依賴mock

          假設(shè)service方法中,又調(diào)用了其他service方法,我們?yōu)榱私档透采w成本,通常會對該service依賴進(jìn)行mock。 譬如上面的爬蟲html解析后,需要進(jìn)行數(shù)據(jù)庫入庫的操作。this.service.githubIssues.create mock后,該方法不會被執(zhí)行, 直接返回create.json數(shù)據(jù),避免了測試數(shù)據(jù)入庫污染。

          it('解析html結(jié)構(gòu)成功'async () => {
            const html = bigoMock.getMockData('githubIssues''html_mock.js'); // 文件mock
            bigoMock.app.mockService("githubIssues""create"'create.json'); // service依賴mock

            const result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);
            bigoMock.assert(result[0].title === 'nginx反向代理實(shí)現(xiàn)線上調(diào)試');
            bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
          });
          復(fù)制代碼

          4.上下文mock

          如果我們想模擬 ctx.user 這個(gè)數(shù)據(jù),也可以通過給 mockContext 傳遞 data 參數(shù)實(shí)現(xiàn)。當(dāng)然,個(gè)人建議service減少上下文依賴,可以通過入?yún)⑦M(jìn)行數(shù)據(jù)傳遞,避免ctx.params.id這類寫法,讓代碼可測試。

          it('should mock ctx.user', () => {
            const ctx = app.mockContext({
              user: {
                name'fengmk2',
              },
            });
            assert(ctx.user);
            assert(ctx.user.name === 'fengmk2');
          });
          復(fù)制代碼

          5.單測數(shù)據(jù)庫

          也有人使用單測數(shù)據(jù)庫,在通過 before 和 after 方法,通在測試開頭創(chuàng)建數(shù)據(jù),結(jié)束的時(shí)候刪掉的。個(gè)人覺得成本較高,單元測試一般不依賴其他接口或者系統(tǒng),mock大法就好了。

          當(dāng)然,實(shí)際的 Service 代碼不會像我們示例中那么簡單,這里只是展示如何測試 Service 而已。更多場景需要大家實(shí)戰(zhàn)補(bǔ)充。

          結(jié)果斷言

          這個(gè)沒有銀彈,通常要結(jié)合業(yè)務(wù)邏輯來編寫。

          // 譬如
          result = {
            statustrue,
            data: {
              age'6',
              name'bigo',
              child: ['bigolive''likee'],
            }
          }

          // 如果是判斷狀態(tài)值,只寫一個(gè)斷言就好
          bigoMock.assert(result.status === true);

          // 如果是部分?jǐn)?shù)據(jù)異常,就需要多個(gè)斷言組合一起
          bigoMock.assert(result.status === true);
          bigoMock.assert(result.data.name === 'bigo');
          復(fù)制代碼

          寫在最后

          測試只是一種手段,而不是目的。

          軟件的質(zhì)量不是測試出來的,而是設(shè)計(jì)和維護(hù)出來的。

          image.png

          延伸閱讀

          更多細(xì)節(jié)請參考,eggjs.org/zh-cn/core/…

          本文單元測試示例代碼來源于:github.com/bigo-fronte…

          歡迎大家留言討論,祝工作順利、生活愉快!

          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認(rèn)真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
          》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持


          瀏覽 54
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  一区二区三区白嫩在线 | 日韩一级电影在线观看 | 在线观看成年人视频 | 精品国产卡一卡二 | 国广富姐搭讪坐顺风车 |