jest 如何运行 以及 node jest 运行 内存不够问题分析

问题简述

因为之前 node jest 一直有 内存泄漏的问题(其实不是内存泄漏),所以研究了下jest 为什么会有这个问题。以及 node 现在 一直runInBand (单个运行),限制了运行的速度。从jest 以及 ts-jest 运行源码出发分析问题。因为只是 从内存方面来理解jest的,所以有很多地方并没有细看,下面在介绍jest的时候 可能不会很全面

jest 简述(和其他测试框架:mocha 比较)

测试框架 , 类似的还有mocha 。 但是jest 明显比mocha 复杂数倍 。mocha 只是基本的测试框架提供了简单的 测试api , jest 可以看作是一个完整的 代码(不止js)运行环境,可以做到编译级别的代码控制

jest 框架 简述 (不考虑 watch 和 覆盖率 模式)

从 cli : jest --config jest.config.json 开始 的运行 步骤

步骤 (function, class等) 步骤内容
run() cli 运行,会在代码里调用这个 function ,主要用来build args 和 获取一些 环境args
runCLI() 这里在获取 输入的args之后,jest 会init config ,从环境变量和 命令指定的dir 或者文件 获取 config , 并且会校验和填充一些args
_run10000 () : 就是这么命名的 创建context ,并且会保存到缓存文件(默认:/tmp/jest_0/ 目录 , linux 系统) , 并且会用 输入的参数做 测试文件filter ,然后是 运行watch 或者 非 watch 模式
runWithoutWatch() 运行非watch模式 jest
runJest () 对test 文件进行处理:排序 , 搜索 。 运行 global hook , init jest 输出 console ,然后 计划 运行 test , 最后对 运行的测试results进行缓存(这个缓存只是为了给 前面的排序使用)
TestScheduler class:测试调度 test的 调度 class , 内部使用 scheduleTests 运行测试,同时负责 reporter init 和 reporter的 event 分发(emit)
scheduleTests() init 测试 数据:测试的empty results , 多线程运行(runInBand),测试运行的 event 分发事件绑定(onResult , onFailure 等) 。 使用代码transform 导入 源文件,init 运行测试的Runner (jest-runner ),对 对应的测试绑定对应的 runner 以及 runner的运行 event (test-file-start/success/failure 等) 。 然后开始运行 tests。运行之后 收集 test 运行 results 进行 合并 以及 对应的event 分发(emit)
Runner.runTests() : Runner 默认 jest-runner 多线程或者 单线程 运行tests (runInBand , 这里只 说明 单线程)
_createInBandTestRun() 分发 test 文件运行的各种 event (test-file-* , onResult , onFailure 等),运行 test (到这里 就是一个 test 文件 ,因为是 runInBand),for 循环运行test
runTest() 运行测试,可以检测内存泄漏(leakDetector)
runTestInternal(): 测试开始运行的核心代码 加载测试必须的核心文件 class : TestEnvironment , TestFramework , Runtime 并且init ,还会检测内存泄漏 和 运行代码覆盖率 。如果使用 gc,会在test 运行之后 执行 gc ,如果使用 logHeapUsage , 会用node 的api 显示 heap 的使用 , 运行之后会对 TestEnvironment clear 注意 加载文件使用 config 配置的 transform ,这也是一个class ,下面讲下 加载非test 和 test 依赖文件的方式在加载文件的时候 , jest 会拦截 require ,然后判断是否需要 做 transform 。如果需要transform,就用transform逻辑。外部使用 requireAndTranspileModule()核心文件 transfrom的逻辑1. 首先 会check 是否存在缓存文件
  1. 不存在缓存,就用 transform 对应的 文件来做 ts/js→js 的操作

  2. 然后对获取的文件保存到对应的缓存目录

  3. 外部使用 requireInternalModule()导入第三方文件 逻辑, 是jest 内部导入的文件1. 对第三文件进行 module 处理,从内存从读取 是否存在

  4. 不存在 继续导入 对应module ,因为文件可能有其他的依赖,所以不是简单的导入,可以理解为 在代码里做了require , 会依次执行文件依赖。通过 node api :vm 和 script 直接运行 导入的源代码 ,同时拦截 源文件的 moduleapi (commonjs 导入模块的 api ) ,对文件的依赖可以依次处理

  5. 对导入的文件 进行transform(transfromfile) , 这里使用 transform ,会对transfrom的 源代码保存到 内存中,因为都是js 所以不会用 transform的逻辑 | | testFramework():jestAdapter | 1. 导入 必须源文件 : jestAdapterInit.js , 运行 fun:initialize() , init 测试必须的 一些function 和参数 (it , describe , skip , only等)。分发 测试 event 和 添加一些 handler ,返回 init 之后的 参数

  6. 运行 global hooks : clear 运行环境

  7. requireModule():导入 test文件 注意这里和 requireInternalModule 逻辑类似,但是这列导入的test 文件 可能是 ts 文件,所以会运行 transform 的逻辑(ts-jest)下面说明 transfrom的逻辑区别1. 运行 scriptTransformer 的 transform

  8. transform 运行 cli 或者 jest.config.json 配置的 transform(ts-jest)

  9. ts-jest 对 ts 文件进行编译 并且 会保存编译文件到缓存目录 ,而且会保存到内存中 (object.create(null) , 无法垃圾回收)

  10. ts-jest 返回 编译信息 , jest 会 保存到缓存目录 和 内存中 ***(map , 无法垃圾回收)***transform 之后 源码会在 vm 和 script 内运行 ,里面会注入 jest 全局对象 , 类似 describe 和 it 会 执行 ,这里会分发event 和执行逻辑。然后会在对应的handler内部收集 test 或者 其他的fn 导入到 对应的 数组或 对象内部 ,这里测试代码是 还没有运行的runAndTransfromResultsToJestFormat():用上面收集的 test fun和数据 运行 测试代码 , 就是一个迭代 运行fun ,然后返回测试 运行的results ,jest的api 都已经 注入 拦截能够 使用 依次返回 函数调用迭代 ,完成 测试代码运行 |

分析node 内存问题

从上面的jest 运行能够分析出,每次运行的代码都会在内存 中,因为server代码在运行的时候 是 依赖整个项目执行的,所以每个测试 都会导入不一样的源码 。jest 会保存 一份 在内存中 ,ts-jest 会保存一份

在内存中,而且不能gc,这就是为什么node 运行之后内存会不够用(不是泄漏,process.exit 之后内存就释放了)。

如果要解决这个问题有几个方案

  1. 大内存运行

  2. node 运行测试的时候不编译源码,使用 和前端类似的模式,只获取 http 。对ts 测试预编译,不使用ts-jest ,所以的运行代码都是 js

  3. 使用多台机器运行 ,直接待用jest api执行 ,减少一台机器的内存使用 (代码已经实现 参考:dev.zstack.io:9080/qing.liang/... jest branch),下面介绍 实现

运行了几个小时之后的 文件缓存,内存缓存 比这个多,还有源码也是保存在内存的

jest 多机器运行

实现思路

通过 启动一个host socket server ,其他多台包括 host 启动 socket client 连接 ,请求 test (从test列表获取) ,然后 client 运行 jest api 返回给 server ,server 对 results 合并,运行完成之后 调用jest html

的api (customer reporter ) 生成 html

下面列出 jest 和 jest html api

function 和 class 使用
socket.io socket class
readConfigs(): Client.ts 获取jest 配置 config
buildArgv(): Client.ts 从cli 构建 jest args
runCLI(): Client.ts 运行 test
jest-html-reporters class : Server.ts jest reporter class
jestReporter.onRunComplete():Server.ts 生成 html
相关推荐
m0_7381207225 分钟前
CTFshow系列——命令执行web38-40
前端·windows·安全·web安全
是小狐狸呀2 小时前
elementUI-表单-下拉框数据选中后,视图不更新
前端·javascript·elementui
四岁半儿5 小时前
常用css
前端·css
你的人类朋友5 小时前
说说git的变基
前端·git·后端
姑苏洛言5 小时前
网页作品惊艳亮相!这个浪浪山小妖怪网站太治愈了!
前端
字节逆旅5 小时前
nvm 安装pnpm的异常解决
前端·npm
Jerry6 小时前
Compose 从 View 系统迁移
前端
GIS之路6 小时前
2025年 两院院士 增选有效候选人名单公布
前端
四岁半儿6 小时前
vue,H5车牌弹框定制键盘包括新能源车牌
前端·vue.js
烛阴6 小时前
告别繁琐的类型注解:TypeScript 类型推断完全指南
前端·javascript·typescript