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>

        如何寫好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ù)雜,代碼改動(dòng)很容易牽一發(fā)動(dòng)全身,單元測試就能給應(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)
        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>
            好大好长好紧爽男 | 天天干天天日 | 强奸久久久久久久 | 亚洲黄色视屏 | 久久伊人精品 | 免费性爱视频在线观看 | 久久朝二| 一区二区无码区 | 伊人大香蕉电影 | 国产刺激出水片 |