ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

为什么并行测试会污染数据库?用 test-queue 的 after_fork 钩子解决全局状态隔离难题

为什么并行测试会污染数据库?用 test-queue 的 after_fork 钩子解决全局状态隔离难题 为什么并行测试会污染数据库用 test-queue 的 after_fork 钩子解决全局状态隔离难题【免费下载链接】test-queueparallel test runner for CI environments项目地址: https://gitcode.com/gh_mirrors/te/test-queuetest-queue 是一款专为 CI 环境设计的 Ruby 并行测试运行器parallel test runner通过中心化队列把测试任务分发给多个 worker。很多新手在开启并行测试后遇到诡异失败数据库被多个进程写花、Redis 缓存串号。本文将解释污染的根源并演示如何用 after_fork 钩子完成全局状态隔离。一、test-queue 是什么test-queue 是面向 CI 的并行测试运行器支持 Minitest、RSpec、Test::Unit、Cucumber 等主流框架。它自带几个开箱即用的命令行工具minitest-queue—— 并行跑 Minitestrspec-queue—— 并行跑 RSpectestunit-queue/cucumber-queue—— 对应 Test::Unit 和 Cucumber它还有一个巧妙设计每次运行的耗时统计会缓存在本地默认.test_queue_stats由 lib/test_queue/stats.rb 实现下次运行时按慢的优先排序入队从而让各 worker 负载更均衡。二、为什么并行测试会污染数据库污染的根本原因只有一句话多个进程共享同一份全局状态。test-queue 采用 master fork worker 模型主进程通过fork(2)派生出多个 worker 子进程核心逻辑在lib/test_queue/runner.rb的spawn_workers方法中。关键点在于——fork之后子进程会原封不动地继承父进程的整个内存空间包括已经建立的 MySQL/PostgreSQL 数据库连接已初始化的 Redis 客户端各种单例对象和全局变量test-queue 的 master 与 worker 进程树示意于是出现这样的场景4 个 worker 全都连在myapp_test这一个数据库上一个 worker 刚插入的用户记录另一个 worker 的断言就读到了——测试彼此干扰失败信息随机出现、难以复现。这就是全局状态污染。三、after_fork 钩子隔离全局状态的钥匙test-queue 为此预留了专门的扩展点。在lib/test_queue/runner.rb中每个 worker 进程在启动后、执行任何测试之前都会调用你覆写的after_fork(num)方法其中num是该 worker 的编号从 1 开始。框架源码对它的注释写得很直白Prepare a worker for executing jobs after a fork.配合另一个钩子prepare(concurrency)在 fork 之前运行用于为每个 worker 预先创建 N 份数据库就能完成完整的隔离方案。推荐的用法是自己写一个自定义 runner 子类例如lib/test_queue/runner/minitest.rb是 Minitest 版本的可继承基类大致思路如下class MyAppTestRunner TestQueue::Runner::Minitest # fork 之前为每个 worker 建好独立数据库 def prepare(concurrency) concurrency.times do |i| # 创建 myapp_test_1 ~ myapp_test_N 并导入 schema end end # fork 之后让本 worker 连到自己的那份数据库 def after_fork(num) ActiveRecord::Base.configurations .configs_for(env_name: test, name: primary).database num.to_s ActiveRecord::Base.establish_connection(:test) $redis.client.db num $redis.client.reconnect end end MyAppTestRunner.new.execute这样worker 1 读写myapp_test_1、worker 2 读写myapp_test_2Redis 也各用各的 db 编号彼此完全隔离污染问题迎刃而解。四、执行流程一图看懂理解下面这条流水线你就完全掌握了钩子的触发时机完整实现见lib/test_queue/runner.rb的execute_internal方法start_master—— master 监听 Unix socket作为任务队列prepare(concurrency)—— ⭐ fork 前一次性创建 N 套数据库/缓存spawn_workers—— fork 出 N 个 workerafter_fork(num)—— ⭐ 每个 worker 内 fork 后重连各自的数据库和 Redisdistribute_queue—— worker 从队列领取测试套件领取逻辑在lib/test_queue/iterator.rb跑完汇总、记录耗时统计一句话记忆prepare 管备料after_fork 管各回各家。五、常用环境变量速查日常使用中还常配合以下环境变量均定义在lib/test_queue/runner.rb中读取变量作用TEST_QUEUE_WORKERS指定 worker 数量默认 CPU 核数TEST_QUEUE_VERBOSE设为 1 可实时查看各 worker 输出TEST_QUEUE_FORCE逗号分隔只跑指定测试套件TEST_QUEUE_STATS统计文件路径默认.test_queue_statsTEST_QUEUE_RELAY分布式模式下把结果中继到中心 master六、小结并行测试污染数据库的根源是fork让多个 worker 继承了同一份数据库连接等全局状态test-queue 用master 预 fork worker 中心队列的架构实现高效并行当前版本 0.12.0见lib/test_queue/version.rb解决方案很简单子类化对应的 runner在prepare中准备好 N 套隔离资源在after_fork中让每个 worker 连上自己那份全局状态隔离难题就此终结 ✅【免费下载链接】test-queueparallel test runner for CI environments项目地址: https://gitcode.com/gh_mirrors/te/test-queue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表