読者です 読者をやめる 読者になる 読者になる

multi_db を使ったアプリのテスト(RSpec)を書く方法

Ruby

開発中のシステムでは multi_db を使ってMaster/Slave型のRDB負荷分散を行っていますが、こういうアプリでRSpecを書くのは一筋縄ではいきません。

Master/Slave型のRDB負荷分散を行うシステムではRDBへのinsert/updateはMaster-DBにのみ行い、selectはSlave-DBに対して行います。 multi_db では、 ドキュメント にあるように、通常のアクセスはSlave-DBに対して行い、コントローラのアクションまたはモデル単位でMaster-DBにのみアクセスするように指定します。

このようなシステムのテストでmulti_db自体が本当にRDBアクセスをMaster-DB、Slave-DBに振り分けているかのテストは不要だと思いますが、アプリ内にMaster-DBにのみアクセスさせる為のコードを書いているので、アプリが本当にMaster-DB、Slave-DBをアクセスしているか確認しておく必要があると思います。


さて、このようなアプリのRSpecを書く方法ですが、

テスト用RDBの構成

  1. テスト環境でも、レプリケーションを行う Master-DB、Slave-DBを準備する
  2. Master、Slave用に2つDatabaseを用意するが、レプリケーションは行わない

の選択があります。MySQLの場合、レプリケーションを行う Master-DB、Slave-DBを用意するのは簡単ではありません。2つDatabaseを用意するのは簡単なので今回は 2. で行います。

テストデータ(fixture)の設定

レプリケーションを行わない場合、どうやってSlave-DBにテストデータ(fixture)を設定するかが問題になります。今回は、before の中で Master-DB、Slave-DB両方にfixtureを読ませる以下のようなコードを書きました。

before do
  @conn_master = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations["test"])
  @conn_slave = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations["test_slave_database_1"])
  @fixtures = {}
  [:a_tables, :b_tables, :c_tables].each do |table|
     Fixtures.create_fixtures(RAILS_ROOT + '/spec/fixtures/', table) {@conn_master}
     f = Fixtures.create_fixtures(RAILS_ROOT + '/spec/fixtures/', table) {@conn_slave}
     @fixtures[table.to_s] = Hash[* f.keys.zip(f.values.map{|e| e.instance_variable_get(:@fixture)}).flatten] 
  end
  @conn_slave.update("update a_tables set name = concat(name,'a')")
  @conn_slave.update("update b_tables set name = concat(name,'a')")
  @conn_slave.update("update c_tables set name = concat(name,'a')")
end
  • ここでは、ちゃんとSlave-DBから読み出されているかを確認する為にSlave-DBのデータは少し変更しています。
  • 通常のRSpecで fixture のデータを a_tables(:one).name のように参照できますが、その代用として @fixures['a_tables']['one']['name'] のように参照できます。

Spec(テスト)の中で 明示的に Master-DBをアクセスしたい場合は @conn_master.select_all() などの ActiveRecord::ConnectionAdapters::DatabaseStatements のメソッドを使います。