テストを書いていると、マルチスレッドでデータベースを参照したくなることがあります。
たとえば
- Capybaraなどによるヘッドレステストを行いたい
- データベースのロック確認
- 複数スレッドでの同時アクセス時の動作
などです。
課題
しかしながら、通常、テスト中のデータベース操作はトランザクション内で行われ、コミットされません。
このため、メインスレッドで行ったデータベース操作は、他のスレッドでは参照できず、テストに失敗します。
こんなときは、
# config.use_transactional_fixtures = true
config.use_transactional_fixtures = false
としてた上でconfig.after(:each)
(あるいはDatabaseCleanerで)データベースを空にするのが定番のやりかたです。
フィクスチャ制御にトランザクションを使わないようにすることで、他のスレッドからも利用できるようになるわけです。
ただし、このやりかただと、テストの度にデータベースを空にする必要があり、トランザクションのロールバックを利用するよりもかなり遅くなってしまう重大な問題がありました。
ほとんどのテストは通常のトランザクションを用いた方法で問題がないわけで、一部のテストのために全体が遅くなるのは許容しがたいです。
特定のSpecでのみuse_transactional_fixtures = falseにする方法
最近はこんな風に、contextごとにuse_truncation: true
というので、使う使わないを選べる様にしています。
require 'rails_helper'
RSpec.describe MyJob, type: :job do
context 'マルチスレッドで複数のコネクションで同時に処理を行う', use_truncation: true do
# この中はテストの実行にトランザクションを使いません。
describe '重複処理があった場合に一方をスキップする' do
# マルチスレッドでActiveRecordを操作するようなテスト
# (snip)
end
describe '...' do
# (snip)
end
end
context 'マルチスレッドで操作しない普通のやつ' do
# この中はテストの実行にトランザクションを使います。(デフォルト動作)
end
end
テスト毎にデータベースを空にするため、DatabaseCleanerを使いますので、そちらもuse_truncation: true
のときはTRANSACTION
ではなくTRUNCATION
を使うようにしておきました。
2017/1/5修正
use_transactional_fixtures = true
の場合にフィクスチャが読み込まれるタイミングとconfig.before(:each) do
のタイミングの問題で正しく初期化できないケースがあったので、use_transactional_fixtures = false
の構成に変更しました。
spec/rails_helper.rb
# spec/rails_helper.rb
# (snip)
require 'spec_helper'
require 'support/use_truncation_context'
# (snip)
RSpec.configure do |config|
# (snip)
config.use_transactional_fixtures = false
# (snip)
end
spec/use_truncation_context.rb
require 'database_cleaner'
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.start
end
config.before(:each, use_truncation: true) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
config.after(:all) do
DatabaseCleaner.clean_with(:truncation)
end
end