<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單元測試

          共 10418字,需瀏覽 21分鐘

           ·

          2021-06-09 11:58

          點擊上方 全棧前端精選,關注公眾號

          回復【1】,加入前端技術交流群

          來源:bigo大魔王

          https://juejin.cn/post/6949084159801294855

          如何寫好eggjs單元測試

          前言

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

          如果你的項目單元測試分支規(guī)范率達到80%以上,我就認為這個同學的代碼質量意識特別好。

          為什么要單元測試

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

          引用eggjs官網的話猛戳這里

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

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

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

          image.png

          測試準備

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

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

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

          自定義mockServiceByData與getMockData

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

          1.新建test/global.ts

          注:如果是bigo內網,可以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) {
              // 默認追加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;
          復制代碼

          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',
                });
            });

          });
          復制代碼

          3.getMockData

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

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

            it('解析html結構成功'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反向代理實現(xiàn)線上調試');
              bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
            });

          });
          復制代碼

          編寫Service單測

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

          當然應用的 Controller、Helper、Extend 等代碼,都必須也有對應的單元測試保證代碼質量。

          綜上,本文會重點講service單測。

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

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

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

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

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

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

            it('解析html結構成功'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反向代理實現(xiàn)線上調試');
              bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
            });

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

          });
          復制代碼

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

          mock輸入

          1.常量mock

          一個service方法,通常有多個arguments,我們在調用service時,可以簡單構造入參

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

          2.文件mock

          如果入參對象較復雜,或者其他單測文件也可以復用,那么使用文件mock比較方便

          it('解析html結構成功'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反向代理實現(xiàn)線上調試');
            bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
          });
          復制代碼

          3.service依賴mock

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

          it('解析html結構成功'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反向代理實現(xiàn)線上調試');
            bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
          });
          復制代碼

          4.上下文mock

          如果我們想模擬 ctx.user 這個數(shù)據(jù),也可以通過給 mockContext 傳遞 data 參數(shù)實現(xiàn)。當然,個人建議service減少上下文依賴,可以通過入參進行數(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');
          });
          復制代碼

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

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

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

          結果斷言

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

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

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

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

          寫在最后

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

          軟件的質量不是測試出來的,而是設計和維護出來的。

          image.png

          延伸閱讀

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

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

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

          瀏覽 37
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  五月婷婷欧美性爱 | 大香蕉在线视频99 | 国产综合丁香五月 | 爱草在线视频 | 丁香婷婷综合久久 |