- 마이그레이션 테스트를 작성해야 하는 경우
- 작동 방식
-
ActiveRecord::Migration
클래스 테스트하기 -
ActiveRecord::Migration
클래스가 아닌 클래스의 테스트하기
GitLab에서 Rails 마이그레이션 테스트하기
신뢰할 수 있는 Rails 마이그레이션을 검사하기 위해서는 데이터베이스 스키마에 대해 테스트해야 합니다.
마이그레이션 테스트를 작성해야 하는 경우
- Post 마이그레이션(
/db/post_migrate
) 및 백그라운드 마이그레이션(lib/gitlab/background_migration
)은 반드시 마이그레이션 테스트를 수행해야 합니다. - 만약 마이그레이션이 데이터 마이그레이션인 경우에는 반드시 마이그레이션 테스트를 수행해야 합니다.
- 다른 마이그레이션은 필요한 경우에만 마이그레이션 테스트를 수행할 수 있습니다.
우리는 스키마 변경만 수행하는 포스트 마이그레이션에 대해서는 테스트를 강제하지 않습니다.
작동 방식
(ee/)spec/migrations/
및 spec/lib/(ee/)background_migrations
의 모든 스펙은 :migration
RSpec 태그와 자동으로 태그가 붙습니다. 이 태그는
spec/support/migration.rb
에서 우리의 커스텀 RSpec before
및 after
훅을 가능하게 합니다. :gitlab_main
이 아닌 데이터베이스 스키마(예: :gitlab_ci
)에 대한 마이그레이션을 수행하려면 반드시 migration: :gitlab_ci
와 같은 RSpec 태그를 명시적으로 지정해야 합니다. 예시는 다음과 같습니다.
spec/migrations/change_public_projects_cost_factor_spec.rb
before
훅은 테스트 중인 마이그레이션 이전으로 데이터베이스의 모든 마이그레이션을 되돌립니다.
즉, 우리의 커스텀 RSpec 훅은 이전 마이그레이션을 찾아 데이터베이스를 다운하여 이전 마이그레이션 버전으로 마이그레이션합니다.
after
훅은 데이터베이스를 업하여 최신 스키마 버전을 복구하여 이 프로세스가 후속 스펙에 영향을 미치지 않고 적절한 격리를 보장합니다.
ActiveRecord::Migration
클래스 테스트하기
ActiveRecord::Migration
클래스(예: 일반 마이그레이션 db/migrate
또는 포스트 마이그레이션 db/post_migrate
)를 테스트하려면 Rails에서 자동으로 로드되지 않으므로 require_migration!
헬퍼 메서드를 사용하여 마이그레이션 파일을 로드해야 합니다.
예시:
require 'spec_helper'
require_migration!
RSpec.describe ...
테스트 헬퍼
require_migration!
마이그레이션 파일은 Rails에 의해 자동으로 로드되지 않기 때문에 마이그레이션 파일을 수동으로 로드해야 합니다. 이를 위해 특정 스펙 파일의 스키마 버전을 포함하는 마이그레이션 파일을 자동으로 로드할 수 있는 require_migration!
헬퍼 메서드를 사용할 수 있습니다.
예를 들어, 파일 이름에 스키마 버전이 포함된 스펙 파일에서 마이그레이션 파일을 로드할 수 있습니다(예: 2021101412150000_populate_foo_column_spec.rb
).
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe PopulateFooColumn do
...
end
일부 경우에는 여러 마이그레이션 파일을 로드하기 위해 스펙에서 여러 번 require_migration!
을 사용해야 할 수도 있습니다. 이 경우에는 스펙 파일과 다른 마이그레이션 파일 사이에 패턴이 없습니다. 다음과 같이 마이그레이션 파일 이름을 명시할 수 있습니다.
# 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!(name: 'gitlab1', path: 'gitlab1')
migrate!
migrate!
헬퍼를 사용하여 테스트 중인 마이그레이션을 실행합니다. 이는 마이그레이션을 실행하고 schema_migrations
테이블에서 스키마 버전을 올리는 역할을 합니다. 이 헬퍼는 after
훅에서 남은 마이그레이션을 실행하기 때문에 어디서 시작해야 하는지 알아야 하기 때문에 필요합니다. 예시:
it 'migrates successfully' do
# ... 마이그레이션 이전 기대사항
migrate!
# ... 마이그레이션 이후 기대사항
end
reversible_migration
change
또는 up
및 down
후크 중 하나 또는 둘 다를 가진 마이그레이션을 테스트하기 위해 reversible_migration
헬퍼를 사용합니다. 이 헬퍼는 마이그레이션이 되돌아가고 어플리케이션 및 데이터의 상태가 최초에 실행됐을 때와 동일하게 되돌아갔는지를 테스트합니다.
-
up 마이그레이션 전에
before
기대사항을 실행합니다. - up 마이그레이션을 실행합니다.
-
after
기대사항을 실행합니다. - down 마이그레이션을 실행합니다.
-
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 작업이 큐에 들어간지 여부를 검증합니다.
이는 순서를 무시하고 배열 인자를 비교할 때 사용합니다.
# 마이그레이션
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 작업이 대기열에 들어갔는지 확인합니다.
이것은 또한 queue_background_migration_jobs_by_range_at_intervals
및 관련된 도우미와 함께 사용할 수 있습니다.
# Migration
BackgroundMigrationWorker.perform_in(delay, 'MigrationClass', args)
# Spec
expect('MigrationClass').to be_scheduled_delayed_migration(delay, *args)
have_scheduled_batched_migration
예상되는 클래스와 인자로 BatchedMigration
레코드가 생성되었는지 확인합니다.
*args
는 MigrationClass
로 전달되는 추가적인 인자이고, **kwargs
는 BatchedMigration
레코드에서 확인해야 하는 다른 속성입니다 (예: interval: 2.minutes
).
# Migration
queue_batched_background_migration(
'MigrationClass',
table_name,
column_name,
*args,
**kwargs
)
# Spec
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
을 호출하는지 확인합니다.
# Migration
finalize_background_migration('MigrationClass')
# Spec
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
...
배경 마이그레이션 예약 테스트 예시
이를 테스트하기 위해서는 보통 다음을 해야 합니다:
- 몇 가지 레코드 생성
- 마이그레이션 실행
- 예상된 작업이 올바른 레코드 세트, 올바른 일괄 크기, 간격 등으로 예약되었는지 확인
배경 마이그레이션의 동작은 배경 마이그레이션 클래스에 대한 별도의 테스트에서 검증되어야 합니다.
이 스펙은
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
...
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
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:merge_requests) { table(:merge_requests) }
let(:group) { namespaces.create!(name: 'gitlab', path: 'gitlab') }
let(:project) { projects.create!(namespace_id: group.id) }
let(:draft_prefixes) { ["[Draft]", "(Draft)", "Draft:", "Draft", "[WIP]", "WIP:", "WIP"] }
def create_merge_request(params)
common_params = {
target_project_id: project.id,
target_branch: 'feature1',
source_branch: 'master'
}
merge_requests.create!(common_params.merge(params))
end
context "for MRs with #draft? == true titles but draft attribute false" do
let(:mr_ids) { merge_requests.all.collect(&:id) }
before do
draft_prefixes.each do |prefix|
(1..4).each do |n|
create_merge_request(
title: "#{prefix} This is a title",
draft: false,
state_id: n
)
end
end
end
it "updates all open draft merge request's draft field to true" do
mr_count = merge_requests.all.count
expect { subject.perform(mr_ids.first, mr_ids.last) }
.to change { MergeRequest.where(draft: false).count }
.from(mr_count).to(mr_count - draft_prefixes.length)
end
it "marks successful slices as completed" do
expect(subject).to receive(:mark_job_as_succeeded).with(mr_ids.first, mr_ids.last)
subject.perform(mr_ids.first, mr_ids.last)
end
end
end
이러한 테스트는 데이터베이스 트랜잭션 내에서 실행되지 않습니다. 삭제 데이터베이스 정리 전략을 사용하기 때문에 트랜잭션에 의존하지 마세요.
deletion_except_tables
에서 시드 데이터를 변경하는 마이그레이션을 테스트할 때, 테스트가 트랜잭션 내에서 실행되고 데이터가 원래 값으로 롤백되도록 :migration_with_transaction
메타데이터를 추가할 수 있습니다.