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

Foreman で Test::mysqld のようにテスト用のデータベースを起動する

testing Perl ruby foreman Thor

Test::mysqld とは

PerlTest::mysqld というモジュールがある。テストを実行するときに MySQLインスタンスをよしなに起動してくれる。

同じテストスイート (プロジェクト、リポジトリ) を並行して実行する場合、データベースの接続先は同じなので複数のテスト実行プロセスが存在すると同じデータベースのインスタンスに接続するため、挿入や削除あるいは更新といった操作を実行すると他のテスト実行プロセスに予期せぬ影響を与えかねない、という問題がある。

Test::mysqld は空いているポートを見つけてそこで listen する MySQL サーバを起動してくれて、環境変数DSN を設定してテストスイートではこの DSN に接続することで、接続先を決めるコードを共有したまま実際に接続する DB のインスタンスを分けることができるようになっている。

その他、機能があるけれど、Test::mysqld の「テスト実行時にデータベースのインスタンスを起動する」という機能が便利そうだと思った。

テスト実行とデータベースの起動

自分は Web アプリケーションを書くことが多く、データの永続化に RDBMS を使うことも多い。

もちろんテストも書くが、テストを実行するときに RDBMS が起動されている必要がある。

OS X だと Launch Agents に登録しておいて勝手に起動するようにしてもいいけれど、あまり無駄なプロセスを立ち上げておきたくもない。

しかし、いちいち手で起動するのもめんどうなので、テストを実行するときに一緒に起動し、テストの実行が終わったら終了するようにしたいと思った。

Foreman を使う

ForemanRuby で書かれたプロセス管理ツールで Procfile に同時に起動するプロセスを書いておくと foreman start で一斉に起動してくれて、いずれかのプロセスが終了したら他のプロセスも終了させてくれるというようなことをしてくれる。

db: postgres -D $POSTGRES_ROOT
test: bundle exec rake test

test/Procfile を上記のような内容で作る。

require 'rake/testtask'
require 'foreman/cli'

Rake::TestTask.new(:test)

namespace :standalone do
  test :test do
    Foreman::CLI.start(%w(start -f test/Procfile))
  end
end

Rakefile に上記のようなタスクを定義して `rake standalone:test` を実行すると Foreman で postgresrake が実行され、rake が終了すると postgres も終了する。

(ちなみに Foreman::CLIThor を継承している。ThorRuby で書かれた CLI アプリケーション・フレームワーク。)

実際のテスト実行のためのタスクは Rake::TestTask によって定義されているけれど、別にテストを実行するタスクの定義の仕方はなんでもいいし、Rake タスクでなくともよい。

課題

この方法では Test::mysqld の機能のほんの一部しか実現していない。

  • テストスイートの実行プロセスごとに接続するインスタンスを個別に起動する
    • foreman は dotenv を使って .env ファイルから環境変数を設定しているのでこれを使えばよさそう
    • standalone:test タスクの中で空いているポートを探して決定されたポート番号を .env に書き出す、という風になると思う
  • データベースの内容をコピーしてデータベースのインスタンスを起動する
    • テストに必要な初期データの用意を速くできる
    • Rails で定義されている Rake タスクで言うところの `seed` みたいなの

上記のような機能は実現していない。