GitLab에서의 Rails 마이그레이션 테스트

신뢰할 수 있는 Rails 마이그레이션을 확인하기 위해서는 데이터베이스 스키마에 대해 테스트해야 합니다.

마이그레이션 테스트를 작성해야 하는 시기

  • Post 마이그레이션(/db/post_migrate) 및 백그라운드 마이그레이션(lib/gitlab/background_migration)은 반드시 마이그레이션 테스트를 수행해야 합니다.
  • 마이그레이션이 데이터 마이그레이션인 경우에는 반드시 마이그레이션 테스트를 수행해야 합니다.
  • 필요한 경우 다른 마이그레이션에 대해서도 마이그레이션 테스트를 수행할 수 있습니다.

우리는 스키마 변경만 수행하는 post 마이그레이션에 대해서는 테스트를 강제로 요구하지 않습니다.

이 방법은 어떻게 작동합니까?

(ee/)spec/migrations/spec/lib/(ee/)background_migrations의 모든 specs는 자동으로 :migration RSpec 태그와 함께 태깅됩니다. 이 태그는 우리의 spec/support/migration.rb에서 사용자 정의 RSpec beforeafter 후크를 활성화시킵니다. :gitlab_main과 같은 데이터베이스 스키마에 대한 마이그레이션을 수행하는 경우 migration: :gitlab_ci와 같이 RSpec 태그를 명시적으로 지정해야 합니다. 예를 들어 참조하려면 spec/migrations/change_public_projects_cost_factor_spec.rb를 참조하세요.

before 후크는 테스트 중인 마이그레이션이 이전 마이그레이션 버전으로 마이그레이트되지 않은 지점으로 모든 마이그레이션을 롤백시킵니다.

즉, 우리의 사용자 정의 RSpec 후크는 이전 마이그레이션을 찾고 데이터베이스를 이전 마이그레이션 버전으로 다운 마이그레이트합니다.

이 접근법을 통해 데이터베이스 스키마에 대해 마이그레이션을 테스트할 수 있습니다.

after 후크는 데이터베이스를 마이그레이트하고 최신 스키마 버전을 복원하여 이 프로세스가 후속 specs에 영향을 미치지 않도록하고 올바른 격리를 보장합니다.

ActiveRecord::Migration 클래스 테스트

ActiveRecord::Migration 클래스(예: db/migrate 또는 db/post_migrate의 일반 마이그레이션)를 테스트하려면 Rails에 의해 자동으로 로드되지 않기 때문에 require_migration! 도우미 메서드를 사용하여 마이그레이션 파일을 로드해야 합니다. 예시:

require 'spec_helper'

require_migration!

RSpec.describe ...

테스트 도우미

require_migration!

마이그레이션 파일은 Rails에 의해 자동으로로드되지 않으므로 마이그레이션 파일을 매뉴얼으로 로드해야 합니다. 이를 위해 require_migration! 도우미 메서드를 사용할 수 있으며, 이를 통해 스펙 파일 이름을 기반으로 올바른 마이그레이션 파일을 자동으로로드할 수 있습니다.

예를 들어 파일 이름에 스키마 버전이 포함된 스펙 파일에서 마이그레이션 파일을 로드하기 위해 require_migration!을 사용할 수 있습니다.

# frozen_string_literal: true

require 'spec_helper'
require_migration!

RSpec.describe PopulateFooColumn do
  ...
end

일부 경우에는 여러 마이그레이션 파일을 요구하여 스펙에서 사용해야 할 수 있습니다. 여기서 스펙 파일과 다른 마이그레이션 파일 사이에는 패턴이 없습니다. 따라서 마이그레이션 파일 이름을 다음과 같이 제공할 수 있습니다.

# frozen_string_literal: true

require 'spec_helper'
require_migration!
require_migration!('populate_bar_column')

RSpec.describe PopulateFooColumn do
  ...
end

table

임시 ActiveRecord::Base 파생 모델을 생성하기 위해 table 도우미를 사용합니다. FactoryBot은 마이그레이션 스펙에 대한 데이터를 생성하는 데 사용하면 안 됩니다. 왜냐하면 애플리케이션 코드에 의존하고 있으며 마이그레이션이 실행된 후 변경될 수 있기 때문에 테스트가 실패할 수 있습니다. 예를 들어 projects 테이블에 레코드를 생성하려면:

project = table(:projects).create!(id: 1, name: 'gitlab1', path: 'gitlab1')

migrate!

테스트 중인 마이그레이션을 실행하려면 migrate! 도우미를 사용합니다. 이는 마이그레이션을 실행하고 schema_migrations 테이블에서 스키마 버전을 업데이트하는 역할을 합니다. after 후크에서 나머지 마이그레이션을 트리거하기 때문에 어디서부터 시작해야 하는지 파악해야 하기 때문에 필요합니다. 예시:

it '성공적으로 마이그레이션됩니다' do
  # ... 마이그레이션 전 기대 동작
  
  migrate!
  
  # ... 마이그레이션 후 기대 동작
end

reversible_migration

change 또는 updown 후크 중 하나 또는 둘 다를 사용하여 마이그레이션을 테스트하기 위해 reversible_migration 도우미를 사용합니다. 이는 마이그레이션이 실행된 후 애플리케이션 및 해당 데이터의 상태가 처음에 마이그레이션이 실행되기 전과 동일한지를 테스트합니다. 이 도우미는 다음을 수행합니다:

  1. Up 마이그레이션 이전의 기대 동작을 수행합니다.
  2. Up 마이그레이션을 실행합니다.
  3. After 기대 동작을 수행합니다.
  4. Down 마이그레이션을 실행합니다.
  5. before 기대 동작을 두 번째로 수행합니다.

예시:

reversible_migration do |migration|
  migration.before -> {
    # ... 마이그레이션 이전 기대 동작
  }
  
  migration.after -> {
    # ... 마이그레이션 후 기대 동작
  }
end

배포 후 마이그레이션에 대한 사용자 정의 매처

우리는 일부 사용자 정의 매처를 spec/support/matchers/background_migrations_matchers.rb에 가지고 있어 배포 후 백그라운드 마이그레이션에 대해 올바르게 예약되었음을 확인합니다. 또한 올바른 수의 인수를 수신했는지 확인합니다.

모든 매처는 be_background_migration_with_arguments라는 내부 매처를 사용하며, 이는 마이그레이션 클래스의 #perform 메서드가 제공된 인수를 수신할 때 크래시하지 않는지를 확인합니다.

be_scheduled_migration

예상된 클래스 및 인수로 Sidekiq 작업이 대기열에 들어갔는지 확인합니다.

이 매처는 보통 우리의 도우미를 통과하지 않고 매뉴얼으로 작업을 대기열에 넣는 경우에 사용됩니다.

# 마이그레이션
BackgroundMigrationWorker.perform_async('MigrationClass', args)

# 스펙
expect('MigrationClass').to be_scheduled_migration(*args)

be_scheduled_migration_with_multiple_args

예상된 클래스 및 인수로 Sidekiq 작업이 대기열에 들어갔는지 확인합니다.

이는 be_scheduled_migration과 같이 작동하지만 배열 인수를 비교할 때 순서가 무시됩니다.

# 마이그레이션
BackgroundMigrationWorker.perform_async('MigrationClass', ['foo', [3, 2, 1]])

# 스펙
expect('MigrationClass').to be_scheduled_migration_with_multiple_args('foo', [1, 2, 3])

be_scheduled_delayed_migration

예상된 지연, 클래스 및 인수로 Sidekiq 작업이 대기열에 들어갔는지 확인합니다.

이는 delay와 관련된 도우미들과 함께 사용할 수 있습니다.

# 마이그레이션
BackgroundMigrationWorker.perform_in(delay, 'MigrationClass', args)

# 스펙
expect('MigrationClass').to be_scheduled_delayed_migration(delay, *args)

have_scheduled_batched_migration

예상된 클래스 및 인수로 BatchedMigration 레코드가 생성되었는지 확인합니다.

*argsMigrationClass에 전달된 추가 인수이고, **kwargsBatchedMigration 레코드에서 확인할 다른 속성입니다(예: interval: 2.minutes).

# 마이그레이션
queue_batched_background_migration(
  'MigrationClass',
  table_name,
  column_name,
  *args,
  **kwargs
)

# 스펙
expect('MigrationClass').to have_scheduled_batched_migration(
  table_name: table_name,
  column_name: column_name,
  job_arguments: args,
  **kwargs
)

be_finalize_background_migration_of

마이그레이션이 finalize_background_migration을 호출하는지 확인합니다.

# 마이그레이션
finalize_background_migration('MigrationClass')

# 스펙
expect(described_class).to be_finalize_background_migration_of('MigrationClass')

마이그레이션 테스트 예시

마이그레이션 테스트는 마이그레이션이 정확히 무엇을 하는 지에 따라 다양한데, 가장 흔한 유형은 데이터 마이그레이션과 백그라운드 마이그레이션을 스케줄링 하는 것입니다.

데이터 마이그레이션 테스트 예시

이 명세는 db/post_migrate/20200723040950_migrate_incident_issues_to_incident_type.rb 마이그레이션을 테스트합니다. 전체 명세는 spec/migrations/migrate_incident_issues_to_incident_type_spec.rb에서 찾을 수 있습니다.

# frozen_string_literal: true

require 'spec_helper'
require_migration!

RSpec.describe MigrateIncidentIssuesToIncidentType do
  ...
end

백그라운드 마이그레이션 스케줄링 테스트 예시

보통 이를 테스트하려면 다음을 수행해야 합니다:

  • 레코드를 생성합니다.
  • 마이그레이션을 실행합니다.
  • 기대한 작업이 올바르게 스케줄링되었는지, 올바른 레코드 세트와 배치 크기, 간격 등이 올바른지 확인합니다.

백그라운드 마이그레이션의 동작 자체는 백그라운드 마이그레이션 클래스에 대한 별도의 테스트에서 확인해야 합니다.

이 명세는 db/post_migrate/20210701111909_backfill_issues_upvotes_count.rb 포스트-배포 마이그레이션을 테스트합니다. 전체 명세는 spec/migrations/backfill_issues_upvotes_count_spec.rb에서 찾을 수 있습니다.

require 'spec_helper'
require_migration!

RSpec.describe BackfillIssuesUpvotesCount do
  ...
end

ActiveRecord::Migration 클래스가 아닌 것을 테스트하는 방법

ActiveRecord::Migration 클래스가 아닌 것(백그라운드 마이그레이션)을 테스트하려면 필요한 스키마 버전을 매뉴얼으로 제공해야 합니다. 스키마를 변경하려는 컨텍스트에 schema 태그를 추가합니다.

설정되지 않으면 schema는 기본적으로 :latest로 설정됩니다.

예시:

describe SomeClass, schema: 20170608152748 do
  # ...
end

백그라운드 마이그레이션 테스트 예시

이 명세는 lib/gitlab/background_migration/backfill_draft_status_on_merge_requests.rb 백그라운드 마이그레이션을 테스트합니다. 전체 명세는 spec/lib/gitlab/background_migration/backfill_draft_status_on_merge_requests_spec.rb에서 찾을 수 있습니다.

# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::BackgroundMigration::BackfillDraftStatusOnMergeRequests do
  ...
end

이러한 테스트는 데이터베이스 트랜잭션 내에서 실행되지 않으므로, 삭제 데이터베이스 정리 전략을 사용합니다. 트랜잭션이 있는 것에 의존하지 마십시오.