본문으로 건너뛰기

테스트 전략

merql은 오라클 기반 테스트 모델을 사용합니다. 각 테스트 시나리오는 입력값(데이터베이스 상태 세 가지)과 결정론적인 예상 결과(오라클)를 정의하고, 실제 병합 결과를 예상 결과와 비교합니다. 32개 시나리오에서 정상 병합, 충돌, 식별성 확인, 데이터 유형, 규모, 경계 사례를 다룹니다.

셸
# Run the full verification suite.
./bin/verify-all

이 페이지의 명령은 merql 저장소를 체크아웃한 상태에서 composer install한 뒤 실행합니다. bin/의 개발 스크립트, scenarios/ 말뭉치, 테스트는 Composer 패키지에 포함되지 않습니다. 개발 의존성에는 PHPUnit 13이 포함되며, 이 버전은 PHP 8.4 이상이 필요합니다. 라이브러리 자체는 PHP 8.2에서 실행됩니다.

오라클 기반 테스트

3방향 데이터베이스 병합을 수행하는 기존 참조 도구가 없으므로, 외부 도구로 결과를 생성하는 대신 알려진 입력에서 오라클을 계산합니다. 각 시나리오에는 논리적으로 검증할 수 있는 결정론적 예상 결과가 있습니다:

  1. 설정 -- 알려진 콘텐츠로 세 가지 데이터베이스 상태(base, ours, theirs)를 만듭니다.
  2. 오라클 -- 알려진 입력에서 예상 병합 결과를 계산합니다.
  3. 실제 결과 -- 세 상태를 대상으로 merql 병합을 실행합니다.
  4. 비교 -- 실제 결과가 예상 결과와 정확히 일치해야 합니다.
코드
Known inputs (base, ours, theirs)
         |                    |
    Deterministic          merql merge
    computation
         |                    |
    Expected result       Actual result
         |                    |
         -----> Compare <-----
                  |
            Pass or Fail

테스트 실행

전체 검증

verify-all 스크립트는 최종 관문입니다. 정적 분석, 코딩 표준, PHPUnit 테스트, 오라클 회귀 테스트 모음을 실행합니다:

셸
./bin/verify-all

작업을 완료한 것으로 간주하려면 이 검사를 통과해야 합니다.

오라클 회귀 테스트 모음

32개 시나리오를 모두 실행합니다:

셸
./bin/test-regression

단일 카테고리 실행:

셸
./bin/test-regression --category clean
./bin/test-regression --category conflict
./bin/test-regression --category identity

카테고리는 scenarios/ 아래의 디렉터리 이름입니다: clean, conflict, identity, types, scale, edge. 알 수 없는 카테고리를 지정하면 No scenarios found을 출력하고 성공적으로 종료합니다. 여러 카테고리를 실행하려면 --category을 반복해서 지정할 수 있습니다.

스크립트는 --jobs도 받지만, 해당 값은 무시하고 시나리오를 차례로 실행합니다(#5).

최소 출력으로 실행(통과/실패만 표시):

셸
./bin/test-regression --fast

단일 시나리오

전체 파이프라인을 통해 시나리오 하나를 테스트합니다:

셸
./bin/test-scenario column-level-clean

개별 오라클 단계

셸
./bin/oracle column-level-clean    # Compute expected result
./bin/actual column-level-clean    # Run merql, capture output
./bin/compare column-level-clean   # Diff oracle vs actual

단위 테스트

셸
composer test:unit         # Isolated component tests
composer test:integration  # Integration tests (SQLite, plus MySQL when configured)
composer test              # All PHPUnit suites
composer test:oracle       # Oracle regression corpus (bin/test-regression)
composer verify            # analyse + cs + test + test:oracle

composer test은 PHPUnit만 실행합니다. oracle PHPUnit 모음은 시나리오 실행기 클래스만 있고 테스트 케이스는 없는 tests/Oracle를 대상으로 하므로, 32개 시나리오는 composer test:oracle, composer verify 또는 ./bin/verify-all을 통해서만 실행됩니다.

MySQL 통합 테스트는 MERQL_MYSQL_HOST, MERQL_MYSQL_DATABASE, MERQL_MYSQL_USER(그리고 선택적으로 MERQL_MYSQL_PASSWORD 및 MERQL_MYSQL_PORT)가 테스트 데이터베이스를 가리키지 않으면 건너뜁니다.

코드 품질

셸
composer cs            # PSR-12 coding standards check
composer cs:fix        # Auto-fix coding standards
composer analyse       # PHPStan level 10 static analysis

시나리오 카테고리

clean (시나리오 8개)

충돌이 없는 깔끔한 병합입니다. 양쪽에서 데이터를 변경할 수 있지만, 같은 행의 같은 열을 동시에 변경하지는 않습니다.

시나리오 테스트
insert-only-theirs theirs에서 행을 삽입하고 ours에서는 변경하지 않음.
update-only-theirs theirs에서 행을 업데이트하고 ours에서는 변경하지 않음.
delete-only-theirs theirs에서 행을 삭제하고 ours에서는 변경하지 않음.
insert-only-ours ours에서 행을 삽입하고 theirs에서는 변경하지 않음.
mixed-no-overlap 양쪽에서 서로 다른 테이블 또는 행을 변경함.
both-same-change 양쪽에서 동일한 변경을 수행함(충돌 없음).
column-level-clean 양쪽에서 같은 행의 서로 다른 열을 변경함.
multi-table 여러 테이블에 걸친 변경.

conflict (시나리오 6개)

충돌이 발생해야 하는 시나리오입니다.

시나리오 테스트
both-update-same-column 양쪽에서 같은 열을 서로 다른 값으로 변경함.
update-vs-delete 한쪽에서 행을 업데이트하고 다른 쪽에서 같은 행을 삭제함.
delete-vs-update 업데이트 대 삭제의 반대 방향.
both-insert-same-pk 양쪽에서 같은 기본 키를 가진 행을 삽입함.
multiple-conflicts 한 번의 병합에서 여러 충돌 발생.
partial-conflict 일부 열은 충돌하고 나머지는 충돌 없이 병합됨.

identity (시나리오 4개)

행 식별의 경계 사례입니다.

시나리오 테스트
auto-increment 새 행의 ID가 각 브랜치에서 서로 다름.
natural-key 기본 키 대신 고유 열로 일치시킴.
composite-key 여러 열로 구성된 기본 키.
no-key 기본 키가 없는 테이블(콘텐츠 해시로 대체).

types (시나리오 6개)

모든 열 유형의 데이터 타입 처리입니다.

시나리오 테스트
text-columns VARCHAR, TEXT, LONGTEXT 값.
numeric-columns INT, DECIMAL, FLOAT 값.
date-columns DATE, DATETIME, TIMESTAMP 값.
json-columns JSON 열 병합.
blob-columns 이진 데이터.
null-handling NULL에서 값으로, 값에서 NULL로 전환.

scale (시나리오 4개)

부하가 걸린 상태의 성능입니다.

시나리오 테스트
1k-rows 1,000개 행.
10k-rows 10,000개 행.
100k-rows 100,000개 행.
wide-table 열이 50개 이상인 테이블.

edge (시나리오 4개)

경계 사례와 경계 조건입니다.

시나리오 테스트
empty-changeset 한쪽 또는 양쪽 모두 변경 사항이 없음.
schema-mismatch 스냅샷 사이에 열이 추가되거나 제거됨.
encoding UTF-8, 이모지, 특수 문자.
large-text 매우 큰 TEXT/LONGTEXT 값.

시나리오 구조

각 시나리오는 scenarios/<category>/<name>/ 아래에 있는 디렉터리이며, scenario.json 설정 파일을 포함합니다:

JSON
{
    "name": "column-level-clean",
    "category": "clean",
    "description": "Both sides modify same row but different columns",
    "tables": ["test_posts"],
    "expectations": {
        "conflicts": 0,
        "operations": 3,
        "changeset_match": "exact",
        "merge_match": "exact",
        "sql_match": "semantic"
    }
}

충돌 시나리오에서는 예상되는 충돌을 자세히 지정합니다:

JSON
{
    "expectations": {
        "conflicts": 1,
        "conflict_details": [
            {
                "table": "test_posts",
                "primary_key": {"id": 42},
                "column": "title",
                "ours_value": "Welcome",
                "theirs_value": "Greetings"
            }
        ]
    }
}

ScenarioRunner 파이프라인

ScenarioRunner::run()은 단일 시나리오를 조율합니다:

  1. OracleCapture::compute()은 시나리오의 JSON 데이터에서 스냅샷 세 개를 만들고 병합을 실행합니다.
  2. ScenarioComparator::compare()는 병합 결과를 scenario.json의 예상 값과 비교합니다.
  3. 실패 메시지 목록과 함께 통과/실패를 반환합니다.
PHP
use Merql\Tests\Oracle\ScenarioRunner;

$result = ScenarioRunner::run($scenario);
// ['name' => 'column-level-clean', 'pass' => true, 'failures' => []]

ScenarioRunner::runAll()은 모든 시나리오를 순서대로 실행합니다:

PHP
$results = ScenarioRunner::runAll($scenarios);

새 시나리오 추가

  1. 디렉터리를 만듭니다: scenarios/<category>/<name>/.
  2. 시나리오 메타데이터와 예상 결과를 담은 scenario.json을 만듭니다.
  3. JSON 설정에서 세 가지 데이터베이스 상태를 정의합니다: base 데이터, ours 변경 사항, theirs 변경 사항.
  4. 시나리오를 실행합니다: ./bin/test-scenario <name>.
  5. 전체 테스트 모음을 확인합니다: ./bin/verify-all.

코드 품질 표준

merql은 다음을 적용합니다:

  • PHPStan 레벨 10 -- 기준 예외 없는 엄격한 정적 분석.
  • PSR-12 -- phpcs로 코딩 표준을 검사합니다.
  • 오라클 회귀 테스트 -- 시나리오 32개를 모두 통과해야 합니다.
  • 단위 테스트 -- 각 구성 요소를 개별적으로 테스트합니다.

verify-all 스크립트는 네 가지 검사를 차례로 실행합니다. 일부 검사만 통과한 상태로 승인할 수 없습니다.

지속적 통합

CI는 모든 푸시와 main에 대한 풀 리퀘스트에서 코딩 표준, PHPStan, composer test, mysqli를 사용하는 MySQL 8.4 대상 통합 테스트, 병합 기본 연산 커버리지, composer audit을 실행합니다. 오라클 회귀 테스트 말뭉치(#6)는 실행하지 않으므로, 변경 사항을 신뢰하기 전에 로컬에서 composer test:oracle 또는 ./bin/verify-all을 실행하세요.