GitLabでRailsマイグレーションをテスト

Railsのマイグレーションを確実にチェックするには、データベースのスキーマに対してマイグレーションをテストする必要があります。

移行テストを書くタイミング

  • ポストマイグレーション(/db/post_migrate)およびバックグラウンドマイグレーション(lib/gitlab/background_migration)は、マイグレーションテストを実施する必要があります
  • 移行がデータ移行の場合は、移行テストが必要です。
  • その他の移行については、必要に応じて移行テストを行うことがあります。

どのように機能するのですか?

:migration タグをテストシグネチャに追加すると、spec/support/migration.rbのカスタム RSpecbeforeafter フックを実行できるようになります。

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 もしくはupdown の両方のフックを持つマイグレーションをテストするにはreversible_migration ヘルパーを使います。 これはマイグレーションが逆転した後のアプリケーションとそのデータの状態が、そもそもマイグレーションが実行される前と同じであることをテストします。 ヘルパーは

  1. アップマイグレーションの前にbefore の期待値を実行します。
  2. 上に移動。
  3. after 期待を実行します。
  4. 下に移動。
  5. 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
注意:データベースの削除クリーンアップを使用するため、これらのテストはデータベーストランザクション内で実行されません。 トランザクションが存在することに依存しないでください。