Turnip, Cucumber などを使った end-to-endテストのテストデータに付いて

Turnip, Cucumber, RSpec feature などを使った end-to-end テスト のテストデータをどう作るかについて、今までの経験を書いてみます。

RSpecなどを使ったモデルのテストであれば、テストデータは factory_girl 等を使い、テストに必要なデータを it, describe単位でテストで作成する事が多いと思います。

f:id:yuum3:20160108112329j:plain

しかし、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