- 마이그레이션 테스트를 작성해야 하는 시점
- 작동 방식
-
ActiveRecord::Migration
클래스 테스트하기 -
ActiveRecord::Migration
클래스가 아닌 클래스를 테스트하는 방법
GitLab에서의 Testing Rails migrations
신뢰할 수 있도록 Rails migration을 테스트하기 위해서는 데이터베이스 스키마에 대한 테스트가 필요합니다.
마이그레이션 테스트를 작성해야 하는 시점
- Post migrations(
/db/post_migrate
) 및 백그라운드 마이그레이션(lib/gitlab/background_migration
) 반드시 마이그레이션 테스트를 수행해야 합니다. - 만약 마이그레이션이 데이터 마이그레이션이라면, 해당 마이그레이션은 반드시 마이그레이션 테스트를 가져야 합니다.
- 그 외의 마이그레이션은 필요한 경우 마이그레이션 테스트를 가질 수 있습니다.
우리는 오직 스키마 변경만을 수행하는 post migrations에 대해서는 테스트를 강제하지 않습니다.
작동 방식
(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
또는 post-migration db/post_migrate
와 같은 일반 마이그레이션)를 테스트하려면 Rails에서 autoload되지 않기 때문에 require_migration!
도우미 메서드를 사용하여 마이그레이션 파일을로드해야 합니다.
예시:
require 'spec_helper'
require_migration!
RSpec.describe ...
테스트 도우미
require_migration!
마이그레이션 파일은 Rails에 의해 자동으로 로드되지 않기 때문에 마이그레이션 파일을 매뉴얼으로 로드해야 합니다. 이를 위해 스펙 파일에 기반하여 올바른 마이그레이션 파일을 자동으로로드할 수 있는 require_migration!
도우미 메서드를 사용할 수 있습니다.
GitLab 14.4 이상에서는 스키마 버전을 파일 이름에 포함하는 스펙 파일에서 마이그레이션 파일을로드하기 위해 require_migration!
을 사용할 수 있습니다. (예: 2021101412150000_populate_foo_column_spec.rb
)
# 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
table
도우미를 사용하여 임시 ActiveRecord::Base
파생 모델을 해당 테이블에 대해 생성할 수 있습니다. FactoryBot
마이그레이션 스펙에 데이터를 생성하는 데 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
reversible_migration
도우미를 사용하여 change
또는 up
및 down
후크 중 하나와 같이 마이그레이션을 테스트할 수 있습니다. 이는 마이그레이션이 역으로 되돌린 후 애플리케이션과 그 데이터의 상태가 처음에 마이그레이션이 실행되었을 당시와 동일한지 테스트합니다. 이 도우미는 다음과 같은 순서로 진행됩니다:
-
up 마이그레이션 이전에
before
기대사항을 실행합니다. - up 마이그레이션을 실행합니다.
-
after
기대사항을 실행합니다. - down 마이그레이션을 실행합니다.
-
before
기대사항을 두 번째로 실행합니다.
예시:
reversible_migration do |migration|
migration.before -> {
# ... pre-migration expectations
}
migration.after -> {
# ... post-migration expectations
}
end
Post-deployment 마이그레이션을 위한 사용자 정의 매처
우리는
spec/support/matchers/background_migrations_matchers.rb
에 몇 가지 사용자 정의 매처를 가지고 있습니다. 이를 통해 post-deployment 마이그레이션으로부터 올바르게 예약되었는지 확인하고 정확한 개수의 인수를 받았는지 확인할 수 있습니다.
이들은 모두 be_background_migration_with_arguments
내부 매처를 사용하며, 이는 제공된 인수를 받았을 때 #perform
메서드가 충돌하지 않는지 확인합니다.
be_scheduled_migration
예상된 클래스와 인수로 Sidekiq 작업이 대기열에 들어갔는지 확인합니다.
이 매처는 일반적으로 우리의 도우미를 통해 매뉴얼으로 작업을 대기열에 넣는 경우에 의미가 있습니다.
# Migration
BackgroundMigrationWorker.perform_async('MigrationClass', args)
# Spec
expect('MigrationClass').to be_scheduled_migration(*args)
be_scheduled_migration_with_multiple_args
예상 클래스와 인수로 Sidekiq 작업이 대기열에 들어갔는지 확인합니다.
이는 be_scheduled_migration
와 같이 작동하지만 배열 인수를 비교할 때 순서가 무시됩니다.
# Migration
BackgroundMigrationWorker.perform_async('MigrationClass', ['foo', [3, 2, 1]])
# Spec
expect('MigrationClass').to be_scheduled_migration_with_multiple_args('foo', [1, 2, 3])
be_scheduled_delayed_migration
소스 브랜치에서 대상 브랜치로 변경 내용을 통합하는 제안입니다.
이를 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
# 코드내용 생략
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
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
이러한 테스트는 데이터베이스 트랜잭션 내에서 실행되지 않습니다. 삭제 데이터베이스 정리 전략을 사용하므로 트랜잭션이 있는 것에 의존하지 마십시오.