点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

来源:bigo大魔王

https://juejin.cn/post/6949084159801294855

如何写好eggjs单元测试

前言

笔者在平时面试前端同学时,经常遇到候选人有nodejs开发经验,但是很少有编写单元测试。希望写下这篇文章,让大家多重视单元测试,交付高质量的代码。

如果你的项目单元测试分支规范率达到80%以上,我就认为这个同学的代码质量意识特别好。

为什么要单元测试

如测试金字塔,单元测试是底座。

引用eggjs官网的话猛戳这里

  • 你的代码质量如何度量?

  • 你是如何保证代码质量?

  • 你敢随时重构代码吗?

  • 你是如何确保重构的代码依然保持正确性?

  • 你是否有足够信心在没有测试的情况下随时发布你的代码?

如果答案都比较犹豫,那么就证明我们非常需要单元测试。

特别是大型nodejs项目,经过多年的代码迭代,业务逻辑复杂,代码改动很容易牵一发动全身,单元测试就能给应用的稳定性提供了一层保障。不用面对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数据比较依赖,需要我们准备大量的json数据,故在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; // 挂载assertasync before() {console.log('hello bigoMock');this.app = app;await app.ready();this.ctx = app.mockContext();return;}/*** 模拟 Service 方法返回值* @param service 方法类* @param methodName 方法名* @param fileName 文件名(状态)*/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数据* @param folder 文件夹* @param fileName 文件名*/getMockData(folder, fileName) {return this.getJson(folder, fileName);}/*** 约定从test/mockData/service/methodName/fileName.json获取数据* @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.jsonbigoMock.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.jsconst html = bigoMock.getMockData('githubIssues', 'html_mock.js');const result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);bigoMock.assert(result[0].title === 'nginx反向代理实现线上调试');bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');});});
复制代码

编写Service单测

如果编写controller单测,从用户请求到达 ==》 返回ctx.body数据,这个过程会涉及Controller、Service、以及下游接口调用等环节。经过的分支逻辑太多,数据会有很多中间状态,这样要准备的单测用例就特别复杂,导致单测分支覆盖率低。但是Service就不一样了,每个Service函数都是单一功能,有明确的输入、输出结果,只要我们的service单元测试代码足够多,单测覆盖率自然就上去了。

当然应用的 Controller、Helper、Extend 等代码,都必须也有对应的单元测试保证代码质量。

综上,本文会重点讲service单测。

如何执行单个测试文件

我们知道执行 npm run test (实际执行 egg-bin test),就会跑全部的测试用例,但是我们通常编写单测时,只关心当前单测的执行情况。我们可以在命令行执行如下命令,执行指定测试文件

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反向代理实现线上调试');bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');});// 只会执行该用例it.only('解析html结构失败', async () => {const html = '';const result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);bigoMock.assert(result.length === 0);});});
复制代码

注:在提交代码前,记得移除only,否则执行npm run test时,只会执行该用例????

mock输入

1.常量mock

一个service方法,通常有多个arguments,我们在调用service时,可以简单构造入参

// 只会执行该用例
it.only('解析html结构失败', async () => {const html = ''; // 常量mockconst 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'); // 文件mockconst result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);bigoMock.assert(result[0].title === 'nginx反向代理实现线上调试');bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
});
复制代码

3.service依赖mock

假设service方法中,又调用了其他service方法,我们为了降低覆盖成本,通常会对该service依赖进行mock。 譬如上面的爬虫html解析后,需要进行数据库入库的操作。this.service.githubIssues.create mock后,该方法不会被执行, 直接返回create.json数据,避免了测试数据入库污染。

it('解析html结构成功', async () => {const html = bigoMock.getMockData('githubIssues', 'html_mock.js'); // 文件mockbigoMock.app.mockService("githubIssues", "create", 'create.json'); // service依赖mockconst result = bigoMock.ctx.service.spider.githubIssues.index.getLinks(html);bigoMock.assert(result[0].title === 'nginx反向代理实现线上调试');bigoMock.assert(result[0].href === 'https://github.com/bigo-frontend/blog/issues/3');
});
复制代码

4.上下文mock

如果我们想模拟 ctx.user 这个数据,也可以通过给 mockContext 传递 data 参数实现。当然,个人建议service减少上下文依赖,可以通过入参进行数据传递,避免ctx.params.id这类写法,让代码可测试。

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

5.单测数据库

也有人使用单测数据库,在通过 before 和 after 方法,通在测试开头创建数据,结束的时候删掉的。个人觉得成本较高,单元测试一般不依赖其他接口或者系统,mock大法就好了。

当然,实际的 Service 代码不会像我们示例中那么简单,这里只是展示如何测试 Service 而已。更多场景需要大家实战补充。

结果断言

这个没有银弹,通常要结合业务逻辑来编写。

// 譬如
result = {status: true,data: {age: '6',name: 'bigo',child: ['bigolive', 'likee'],}
}// 如果是判断状态值,只写一个断言就好
bigoMock.assert(result.status === true);// 如果是部分数据异常,就需要多个断言组合一起
bigoMock.assert(result.status === true);
bigoMock.assert(result.data.name === 'bigo');
复制代码

写在最后

测试只是一种手段,而不是目的。

软件的质量不是测试出来的,而是设计和维护出来的。

image.png

延伸阅读

更多细节请参考,eggjs.org/zh-cn/core/…

本文单元测试示例代码来源于:github.com/bigo-fronte…

欢迎大家留言讨论,祝工作顺利、生活愉快!

如果觉得这篇文章还不错

点击下面卡片关注我

来个【分享、点赞、在看】三连支持一下吧

 “分享、点赞、在看” 支持一波 

如何写好 eggjs 单元测试相关推荐

  1. 如何写好eggjs单元测试

    本文首发于:https://github.com/bigo-frontend/blog/ 欢迎关注.转载. 如何写好eggjs单元测试 前言 笔者在平时面试前端同学时,经常遇到候选人有nodejs开发 ...

  2. nodejs异常处理过程/获取nodejs异常类型/写一个eggjs异常处理中间件

    前言 今天想写一下eggjs的自定义异常处理中间件,在写的时候遇到了问题,这个错误我捕获不到类型?? 处理过程,不喜欢看过程的朋友请直接看解决方法和总结 看一下是什么: 抛出的异常是检验失败异常Val ...

  3. python测试代码怎么写_Python 单元测试

    Test your software, or your users will. "Test ruthlessly. Don't make your users find bugs for y ...

  4. 如何写出优秀的单元测试

    单元测试已是软件工程师必备的技能,但在我的经验中,有些人写的单元测试实际上却没测到重点,而且还容易因为重构而导致测试失败,可说是为了测试而测试.这样的测试不仅不会带来好处,反而还使专项更不稳健,因此遵 ...

  5. 如何写好测试用例以及go单元测试工具testify简单介绍

    背景 ​ 最近在工作和业余开源贡献中,和单元测试接触的比较频繁.但是在这两个场景之下写出来的单元测试貌似不太一样,即便是同一个代码场景,今天写出来的单元测试和昨天写的也不是很一样,我感受到了对于单元测 ...

  6. 前端单元测试怎么写(以Vue为例)

    单元测试是什么 对软件中的最小可测试单元(一个方法)进行测试 单元测试的意义 1.分模块开发,方便定位到哪个模块出现问题 2.保证了代码质量 3.驱动开发(先写单元测试,通过再写代码) 单元测试两种类 ...

  7. java如何写单元测试_java如何使用JUnit进行单元测试

    注:所有内容都是在eclipse上实现,关于eclipse的安装和jdk的安装配置,请看:http://www.cnblogs.com/fench/p/5914827.html 单元测试是什么? 百度 ...

  8. pringboot 单元测试 空指针_单元测试中的 FIRST 原则

    单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证.进行单元测试,可以尽早地发现编写代码中错误,减少后期测试开销和维护成本,提高软件质量. 下文讲解写出好单元测试需要遵守 ...

  9. 单元测试Struts2的Action(包含源码)

    很久没有从头搭建Struts2的环境了.最近,认真实践了单元测试Struts2.Spring等Java项目. 今天特意写的是单元测试Struts2的Action,遇到了不少问题,果然是实践出真知啊. ...

最新文章

  1. ubuntu mysql vi_Ubuntu16 下安装 mysql
  2. 头条号个人中心登录_微信个人订阅号开通了创作领域的认证!这是要跟头条看齐了吗?...
  3. linux c 之使用-O来优化gcc
  4. oracle之数据处理
  5. excel字符处理函数
  6. 笔记《深入浅出数据分析》上
  7. FocusLab新生大礼包三:Latex安装教程
  8. 计算机设备码的功能,电脑机器码,详细教您电脑机器码修改软件
  9. php股票指标,最精准的换手率选股法股票指标 通达信公式(附图)
  10. 概览:可视化前端测试
  11. 咸阳无房证明网上办理指南
  12. 华为OD机试用Python实现 -【查找树中的元素 or 查找二叉树节点】(2023-Q1 新题)
  13. UVA, 516 Prime Land
  14. PyTorch学习笔记(19) ——NIPS2019 PyTorch: An Imperative Style, High-Performance Deep Learning Library
  15. 程序员之禅的10条黄金法则
  16. Linux rsync命令
  17. PHP扩展undefined symbol,基于phpx的扩展运行报错undefined symbol
  18. Windows Vista正式版何时提供下载
  19. Latex编译成功但是无法输出到PDF
  20. CSS奇思妙想 -- 使用 background 创造各种美妙的背景

热门文章

  1. 大学生利用漏洞“骗走”京东110万, 中心化白条的漏洞, 区块链能否补得上?
  2. hp z240工作站安装windows7操作系统方法
  3. 【笔记】关于几个机械臂模型的正解反解,不同的运动求解方式V-REP中
  4. 产品营销新招:短视频营销策略分析教你轻松占领市场!
  5. 鼠标滚轮消息WM_MOUSEWHEEL
  6. pyhon使用pip安装卸载selenium和安装firefox驱动,及使用selenium启动firefox浏览器
  7. R语言acres92 region_R语言学习笔记(四)
  8. java超市进销存系统_Java超市进销存系统完整版JAVA源码下载
  9. TreeSet的自然排序和定制排序
  10. Go语言接入支付宝开放平台