一名好的大前端开发人员,一定是一名好的“配置工程师”(滑稽脸)。而最近刚到团队,被安排给 vemoJS 和 cloudbase-cli 写测试用例,并且要保证覆盖率!
这里主要以 vemojs 下的测试用例为主来讲解 Jest 要注意的地方。测试代码在:https://github.com/vemoteam/vemo/tree/master/test
观察 vemojs 这个项目,如果想进行全面测试,需要解决以下问题:
针对以上问题,解决思路总结如下:
axios
就是被 mock 的axios
等第三方请求库请求服务针对上面的步骤以及核心的 jest 配置,分别做讲解。
jest 提供两种方式来让用户自定义配置,一个是根目录的 jest.config.js
,另一个是启动 jest 的时候给参数。我是采用两者混搭的方法。
jest.config.js
:在统计覆盖率的时候,忽略 test
和 node_modules
文件夹下。有时候为了方便,会把测试常用的函数、配置放在 test 目录下,如果不忽略,会被统计进去,但它不属于源码部分。除此之外,别忘了 node_modules,否则由于文件太多,根本启动不起来,而且结果也不对。
module.exports = {coveragePathIgnorePatterns: ["<rootDir>/test/", "<rootDir>/node_modules/"]};
命令行参数写在 package.json
文件的 scripts
属性中。 需要注意的地方有 2 个, --detectOpenHandles
参数是为了当句柄未正常关闭,显式报错给用户; --env=node
指明测试环境是 nodejs,默认是浏览器。
"scripts": {"test": "jest --passWithNoTests --coverage --env=node --detectOpenHandles"}
这个很简单,但是可以配合 describe
关键字,层级区分测试逻辑。还可以配合 beforeAll 等生命周期钩子函数,提高测试效率。
const { VemoError } = require("./../src/error");describe("error.js", () => {test("Throw VemoError", () => {function throwVemoError() {throw new VemoError();}expect(throwVemoError).toThrow(Error);expect(throwVemoError).toThrow(VemoError);});test("VemoError should have code and message", () => {const properties = ["code", "message"];const vemoError = new VemoError();expect(properties.every(prop => vemoError.hasOwnProperty(prop))).toBe(true);});});
有一些函数需要连接云的 API 进行认证,由于安全策略,不在云厂商的服务器上无法请求。这时候,就需要 mock 对应的请求库,返回我们构造好的数据,以让函数逻辑走下去,提高测试覆盖率。
jest.mock("axios");test("getTempSecret should get tencent cloud temporary secret", async () => {// 下面就是mock的数据axios.get.mockResolvedValue({data: {TmpSecretId: "testTmpSecretId",TmpSecretKey: "testTmpSecretKey",Token: "testToken",ExpiredTime: Date.now()}});await cloudBaseMiddleware({}, async () => {});});
这方面很多人可能会用 supertest 这个库来测试。在做调研的时候发现,jest 的下载量和更新记录远远高于 supertest,而且更纯粹。为什么这么说呢?它提供一种测试的组织形式,其它可以借助第三方库和工具实现。
而服务测试的思路就是:在 test 目录下启动简单的 http 服务器和静态服务器,然后利用 axios
访问启动的服务器,拿到返回结果,再利用断言的写法,检查即可。
请看下面这段代码:
require("./../../src/"); // 启动服务器// 加载配置文件和axios库const axios = require("axios");const config = require("./vemofile");const instance = axios.create({baseURL: `http://${config.host}:${config.port}`});// 下面分别请求:/home#GET 和 /api#POST 接口,并且检查返回结果describe("index.js api server", () => {test("template response should be HTML", async () => {expect.assertions(2);const { data, status } = await instance.get("/home");expect(status).toEqual(200);expect(data).toMatch(/<html>.*<\/html>/is);});test("post and validate check", async () => {expect.assertions(2);const params = {param1: "test",param2: 123};const { data, status } = await instance.post("/api", params);expect(status).toEqual(200);expect(JSON.stringify(data)).toEqual(JSON.stringify(params));});});
针对 ws 协议,测试它的思路有点像 SSR:
/ws
路由上启动 ws 协议,在 2s 后,会向链接的客户端主动发送消息/ws
主动传来的数据,然后更新页面内容具体请看:https://github.com/vemoteam/vemo/blob/master/test/server/index.test.js 的 61 ~ 91 行
由于 windows 下 puppeteer 无法通过 npm 下载安装(就是很麻烦),所以把 puppeteer 的加载代码进一步处理,同时在失败的时候给出友好的提示,引导使用者切换测试平台:
// ... other codesasync function launchBrowser() {try {const puppeteer = require("puppeteer");const browser = await puppeteer.launch();return browser;} catch (error) {// if load fail, show information and return immediatelyconsole.log("Don't run test in Windows. \n" +"If you fail to launch on UNIX, please install dependencies. \n" +"More info: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md. \n");return null;}}const browser = await launchBrowser();// ... other codes
http 服务器、静态服务器和 ws 服务器对应的启动文件 /src/index.js
没有对外暴露接口,没法显示传入要求的配置文件: vemofile.js
,它只能自动读取。
而在运行测试的时候,它会在根目录下读取 vemofile.js
,而我们的配置写在 /test/serve
下,所以要手动切换一下运行目录: process.chdir(__dirname)
。这样就保证了针对测试服务器的配置不会污染代码库。
用户在安装库的时候,显然不需要跑测试,所以需要让 npm 忽略 test 目录下的文件(其实对于一些 ts 的项目,src 下的源码也是忽略的)。给 .npmignore
添加如下内容:
# testtest
最后放一下覆盖率统计效果吧(Ubuntu 16.04):
没覆盖的地方,全部是出现异常地方。一般来说超过 80%的覆盖率即可,其他的可以慢慢补上。这种自己手动跑的方式太 low 了,之后还会有一篇讲解 CI 等第三方工具的文章,“懒就是生产力”。