Turnip, Cucumber などを使った end-to-endテストのテストデータに付いて
Turnip, Cucumber, RSpec feature などを使った end-to-end テスト のテストデータをどう作るかについて、今までの経験を書いてみます。
RSpecなどを使ったモデルのテストであれば、テストデータは factory_girl 等を使い、テストに必要なデータを it, describe単位でテストで作成する事が多いと思います。
しかし、end-to-endテストでは人がそのアプリを使う手順、シナリオに沿ってのテストにり、モデルのテストのように個々のテストで個別のデータを必要になることはあまりなく、いくつかのデータを作っておけば済みます。また、モデルのテストでは必要になるモデル(テーブル)は少数ですがend-to-endテストではほとんどのモデル(テーブル)にデータが必要になります。
そこで、今まで作ったアプリのend-to-endテスト用のデータをどのように準備したかを、まとめてみました。またテストデータではありませんが、開発時に使う db_seed についてもふれています。
1. Cucumber + fixture + db_seedなし
Rails 2の頃に作ったアプリで標準の fixture を使っています。なんとモデルも fixture を使っているので fixture はかなりカオスな状況になっています ^^;
また、db_seed については知らなかったので、 rake db:fixtures:load
を使っていました。 fixture は人手でデータを作るので テストや開発に必要な意味のあるデータを書くのには向いていると思います。
ただし、テスト用にデータを100件準備するとかには向きません(ERBが書けるので出来ないわけではないですが)。
2. RSpec feature + factory_girl + db_seed
この開発では end-to-endテストは RSpec feature を使い、テスト用のデータは factory_girl を使って作っていますが、helperメソッド内で全モデル用のデータを作り全end-to-endテストで同じものを使っています。 db_seed はActiveRecordのメソッドだけで別に作りました。
3. RSpec feature + fixture + db_seedなし
これは、1. のシステムをRails4にバージョンアップする際に管理画面の end-to-endテストを追加した際に作りました。従ってテストデータは 1. と同じです。
4. RSpec feature + factory_girl + factory_girl を使ったdb_seed
db_seed と end-to-endテストのデータは同じもで良いのでは? または db_seed は end-to-endテストのデータ + アルファ なのでは? という仮定のもとに db_seedもfactory_girl を使って作りました。 同じようなものを2つ作らずに済んだので良いアプローチだったと思います。
また、end-to-endテストを作る際には実際にブラウザーを操作し、画面や DOM を見ながらテストを作るのでdb_seed と end-to-endテストのデータが同じだと作業がはかどりました。
5. Turnip + factory_girl + db_seed
この案件は既存のアプリの改善だったので、既に db_seed はあるが end-to-endテストは無い状態だったので end-to-endテストのデータは 2. と同じように作りました。
ただし、 4. に書いたようにテストを書く際にテスト用データでアプリを動かせると便利なので、それを行うツールを作りテストアプリ作成に役立てました。
ツールは以下のようなコードで、load_turnip_seed で Turnip 用に書いたテストデータ作成メソッド turnip_seed_data() を呼び出していま。turnip_seed_data() は通常のfactory_girlを使ったコードです。 テストデータを消すための truncate というメソッドも作っておきました。
# Usage: # rails runner Tasks::DbTool.truncate # rails runner Tasks::DbTool.load_turnip_seed $LOAD_PATH << "#{Rails.root}/spec" # TODO: もっと良いやり方 require 'turnip_seed_data' class Tasks::DbTool extend FactoryGirl::Syntax::Methods def self.load_turnip_seed self.truncate turnip_seed_data() end def self.truncate ActiveRecord::Base.connection.tables.reject{|t| t == 'schema_migrations'}.each do |table| # MySQL only ActiveRecord::Base.connection.execute("TRUNCATE TABLE `#{table}`") end end end