GitLabでRailsマイグレーションをテスト
Railsのマイグレーションを確実にチェックするには、データベースのスキーマに対してマイグレーションをテストする必要があります。
移行テストを書くタイミング
- ポストマイグレーション(
/db/post_migrate
)およびバックグラウンドマイグレーション(lib/gitlab/background_migration
)は、マイグレーションテストを実施する必要があります。 - 移行がデータ移行の場合は、移行テストが必要です。
- その他の移行については、必要に応じて移行テストを行うことがあります。
どのように機能するのですか?
:migration
タグをテストシグネチャに追加すると、spec/support/migration.rb
のカスタム RSpecbefore
とafter
フックを実行できるようになります。
before
フックは、テスト中のマイグレーションがまだマイグレーションされていない時点まで、すべてのマイグレーションを差し戻します。
言い換えれば、私たちのカスタムRSpecフックは以前のマイグレーションを見つけ、以前のマイグレーションバージョンまでデータベースをマイグレーションします。
このアプローチでは、データベーススキーマに対してマイグレーションをテストできます。
after
フックはデータベースをマイグレートし、最新のスキーマ・バージョンに戻すので、このプロセスが後続の仕様に影響を与えることはなく、適切な分離が保証されます。
ActiveRecord::Migration
クラスのテスト
ActiveRecord::Migration
クラス (通常のマイグレーションdb/migrate
やマイグレーション後のdb/post_migrate
など) をテストするには、マイグレーションファイルが Rails で自動ロードされないため、手動でrequire
する必要があります:
require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb')
テストヘルパー
table
テーブルの一時的なActiveRecord::Base
-derived モデルを作成するには、table
ヘルパーを使用します。FactoryBotは、マイグレーション仕様のデータを作成するために使用しないでください。 たとえば、projects
テーブルにレコードを作成する場合などです:
project = table(:projects).create!(id: 1, name: 'gitlab1', path: 'gitlab1')
migrate!
テスト中のマイグレーションを実行するには、migrate!
ヘルパーを使います。これはマイグレーションを実行するだけでなく、schema_migrations
テーブルのスキーマバージョンもバンプします。after
フックで残りのマイグレーションをトリガーするので、どこから始めるかを知る必要があるからです。 例を挙げます:
it 'migrates successfully' do
# ... pre-migration expectations
migrate!
# ... post-migration expectations
end
reversible_migration
change
もしくはup
とdown
の両方のフックを持つマイグレーションをテストするにはreversible_migration
ヘルパーを使います。 これはマイグレーションが逆転した後のアプリケーションとそのデータの状態が、そもそもマイグレーションが実行される前と同じであることをテストします。 ヘルパーは
-
アップマイグレーションの前に
before
の期待値を実行します。 - 上に移動。
-
after
期待を実行します。 - 下に移動。
-
before
、2回目の期待を実行します。
使用例:
reversible_migration do |migration|
migration.before -> {
# ... pre-migration expectations
}
migration.after -> {
# ... post-migration expectations
}
end
データベース移行テスト例
この仕様は、db/post_migrate/20170526185842_migrate_pipeline_stages.rb
の移行をテストするものです。完全な仕様は、spec/migrations/migrate_pipeline_stages_spec.rb
にあります。
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb')
RSpec.describe MigratePipelineStages do
# Create test data - pipeline and CI/CD jobs.
let(:jobs) { table(:ci_builds) }
let(:stages) { table(:ci_stages) }
let(:pipelines) { table(:ci_pipelines) }
let(:projects) { table(:projects) }
before do
projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
end
# Test just the up migration.
it 'correctly migrates pipeline stages' do
expect(stages.count).to be_zero
migrate!
expect(stages.count).to eq 2
expect(stages.all.pluck(:name)).to match_array %w[test build]
end
# Test a reversible migration.
it 'correctly migrates up and down pipeline stages' do
reversible_migration do |migration|
# Expectations will run before the up migration,
# and then again after the down migration
migration.before -> {
expect(stages.count).to be_zero
}
# Expectations will run after the up migration.
migration.after -> {
expect(stages.count).to eq 2
expect(stages.all.pluck(:name)).to match_array %w[test build]
}
end
end
ActiveRecord::Migration
以外のクラスのテスト
ActiveRecord::Migration
以外のテスト(バックグラウンドでの移行)を行うには、必要なスキーマバージョンを手動で提供する必要があります。 データベーススキーマを切り替えたいコンテキストにschema
タグを追加してください。
設定されていない場合、schema
のデフォルトは:latest
です。
使用例:
describe SomeClass, schema: 20170608152748 do
# ...
end
バックグラウンド移行テスト例
この仕様では、lib/gitlab/background_migration/archive_legacy_traces.rb
のバックグラウンドマイグレーションをテストします。spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
require 'spec_helper'
describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, schema: 20180529152628 do
include TraceHelpers
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:builds) { table(:ci_builds) }
let(:job_artifacts) { table(:ci_job_artifacts) }
before do
namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123)
@build = builds.create!(id: 1, project_id: 123, status: 'success', type: 'Ci::Build')
end
context 'when trace file exists at the right place' do
before do
create_legacy_trace(@build, 'trace in file')
end
it 'correctly archive legacy traces' do
expect(job_artifacts.count).to eq(0)
expect(File.exist?(legacy_trace_path(@build))).to be_truthy
described_class.new.perform(1, 1)
expect(job_artifacts.count).to eq(1)
expect(File.exist?(legacy_trace_path(@build))).to be_falsy
expect(File.read(archived_trace_path(job_artifacts.first))).to eq('trace in file')
end
end
end