
# テスト戦略

merqlはオラクル駆動のテストモデルを採用しています。各テストシナリオでは、既知の入力（3つのデータベース状態）と決定的な期待結果（オラクル）を定義し、実際のマージ出力と期待結果を比較します。32のシナリオで、競合のないマージ、競合、識別情報の解決、データ型、規模、エッジケースを網羅します。

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

このページのコマンドは、merqlリポジトリをチェックアウトし、`composer install`を実行した状態で使用します。`bin/`の開発用スクリプト、`scenarios/`コーパス、テストはComposerパッケージに含まれません。開発依存関係にはPHPUnit 13が含まれ、PHP 8.4以降が必要ですが、ライブラリ自体はPHP 8.2で動作します。

## オラクル駆動テスト

3方向のデータベースマージを行う既存の参照ツールはないため、オラクルは外部ツールで生成するのではなく、既知の入力から算出します。各シナリオには、ロジックで検証できる決定的な期待結果があります。

1. **セットアップ** -- 既知の内容で3つのデータベース状態（base、ours、theirs）を作成します。
2. **オラクル** -- 既知の入力から期待されるマージ結果を算出します。
3. **実行結果** -- merqlで3つの状態をマージします。
4. **比較** -- 実際の結果が期待結果と完全に一致することを確認します。

```
Known inputs (base, ours, theirs)
         |                    |
    Deterministic          merql merge
    computation
         |                    |
    Expected result       Actual result
         |                    |
         -----> Compare <-----
                  |
            Pass or Fail
```

## テストの実行

### 全体の検証

`verify-all`スクリプトが最終ゲートです。静的解析、コーディング規約、PHPUnitテスト、オラクル回帰テストスイートを実行します。

```bash
./bin/verify-all
```

作業を完了とみなす前に、これがパスする必要があります。

### オラクル回帰テストスイート

32のシナリオすべてを実行します。

```bash
./bin/test-regression
```

カテゴリを1つ指定して実行します。

```bash
./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`も受け付けますが、値は無視し、シナリオを1つずつ実行します（[#5](https://github.com/inline0/merql/issues/5)）。

出力を最小限にして実行します（成功/失敗のみ）。

```bash
./bin/test-regression --fast
```

### 単一シナリオ

全工程を通して1つのシナリオをテストします。

```bash
./bin/test-scenario column-level-clean
```

### 個別のオラクル手順

```bash
./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
```

### 単体テスト

```bash
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`）がテストデータベースを指している場合にのみ実行されます。

### コード品質

```bash
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` | 1回のマージで複数の競合が発生。 |
| `partial-conflict` | 一部のカラムは競合し、ほかは競合なくマージ。 |

### identity（4シナリオ）

行識別に関するエッジケースです。

| シナリオ | テスト内容 |
|---|---|
| `auto-increment` | 新しい行のIDがブランチ間で異なる。 |
| `natural-key` | PKではなく一意のカラムで照合。 |
| `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データから3つのスナップショットを作成し、マージを実行します。
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設定で3つのデータベース状態を定義します：baseデータ、oursの変更、theirsの変更。
4. シナリオを実行します：`./bin/test-scenario <name>`。
5. テストスイート全体を検証します：`./bin/verify-all`。

## コード品質基準

merqlでは次の基準を適用します。

- **PHPStanレベル10** -- 例外のベースラインなしで厳密に静的解析します。
- **PSR-12** -- `phpcs`でコーディング規約を確認します。
- **オラクル回帰テスト** -- 32のシナリオすべてに合格する必要があります。
- **単体テスト** -- 各コンポーネントを個別にテストします。

`verify-all`スクリプトは4つのチェックを順番に実行します。一部のみの承認は認められません。

## 継続的インテグレーション

CIは、`main`への各プッシュとプルリクエストで、コーディング規約、PHPStan、`composer test`、mysqli経由でMySQL 8.4に対する統合テスト、マージプリミティブのカバレッジ、`composer audit`を実行します。オラクル回帰コーパス（[#6](https://github.com/inline0/merql/issues/6)）は実行しないため、変更を信頼する前に、ローカルで`composer test:oracle`または`./bin/verify-all`を実行してください。
