含Ruby Thread的Sidekiq Worker执行RSpec测试失败该如何处理
RSpec测试含Ruby多线程代码的解决方案
问题根源
对象为空报错并非RSpec不支持多线程,核心原因是默认开启的事务性测试机制:
RSpec默认的事务性测试会将所有测试操作放在一个未提交的数据库事务中,事务仅绑定测试运行的主线程,自定义创建的
Thread子线程无法读取主线程中未提交的事务数据,因此读取对象时返回空。
方案1:单元测试场景(推荐优先使用)
单元测试仅需验证业务逻辑正确性,无需实际触发多线程运行,直接Stub线程方法即可,无需修改测试框架配置,运行速度快,完全规避多线程数据可见性问题。
修改后的测试用例示例:
it 'creates notification' do # 新增Stub逻辑:让Thread.new直接同步执行代码块,不创建真实子线程 allow(Thread).to receive(:new) do |&block| block.call end job = create(:job) notification = create(:notification) devices = create(:device) scores = ScoringService.new(job, 25).score worker = ScoreWorker.new expect do worker.perform(scores, notification, devices) end.to change(Notification, :count).by(1) end
方案2:需要真实测试多线程行为的场景
如果要验证多线程运行的实际效果,需要关闭事务性测试,改用数据库截断策略,让所有线程都能读取到已写入数据库的真实数据。
第一步:配置数据库清理策略
在spec/rails_helper.rb中添加如下配置:
RSpec.configure do |config| # 关闭全局事务性测试 config.use_transactional_fixtures = false config.before(:suite) do DatabaseCleaner.strategy = :truncation end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end end
第二步:调整原测试用例
删除测试代码中的self.use_transactional_fixtures配置即可,其余逻辑无需改动。
注意:截断策略会真实写入并清空数据库,测试运行速度会比事务策略慢,仅在必要时使用。
额外优化建议
现有Worker的多线程实现没有利用到scores参数,每个线程执行的逻辑完全相同,会重复推送相同通知,建议调整为每个线程处理单条score数据:
def perform(scores, notification, devices) threads = [] scores.each do |score| # 把score传入服务,避免重复执行相同逻辑 threads << Thread.new { PushNotificationService.new(notification, devices, score).deliver } end threads.each(&:join) end
内容的提问来源于stack exchange,提问作者Kingsley Simon
相关产品推荐
相关产品推荐

