Teststrategie
merql verwendet ein testmodell auf Grundlage von Orakeln. Jedes Testszenario legt bekannte Eingaben (drei Datenbankzustände) und ein deterministisches erwartetes Ergebnis (das Orakel) fest und vergleicht die tatsächliche Merge-Ausgabe damit. 32 Szenarien decken saubere Merges, Konflikte, Identitätsauflösung, Datentypen, Skalierung und Sonderfälle ab.
# Run the full verification suite.
./bin/verify-all
Die Befehle auf dieser Seite werden in einem Checkout des merql-Repositorys nach composer install ausgeführt. Die Entwicklungsskripte in bin/, das Korpus scenarios/ und die Tests gehören nicht zum Composer-Paket. Zu den Entwicklungsabhängigkeiten gehört PHPUnit 13, das PHP 8.4 oder höher benötigt, obwohl die Bibliothek selbst mit PHP 8.2 läuft.
Testen auf Grundlage von Orakeln
Es gibt kein vorhandenes Referenzwerkzeug für Dreiwege-Merges von Datenbanken. Daher wird das Orakel aus bekannten Eingaben berechnet und nicht von einem externen Werkzeug erzeugt. Jedes Szenario hat ein deterministisches erwartetes Ergebnis, das sich anhand der Logik überprüfen lässt:
- Einrichtung -- drei Datenbankzustände (Basis, uns, sie) mit bekannten Inhalten erstellen.
- Orakel -- das erwartete Merge-Ergebnis aus den bekannten Eingaben berechnen.
- Tatsächliches Ergebnis -- den Merge der drei Zustände mit merql ausführen.
- Vergleich -- das tatsächliche Ergebnis muss exakt mit dem erwarteten übereinstimmen.
Known inputs (base, ours, theirs)
| |
Deterministic merql merge
computation
| |
Expected result Actual result
| |
-----> Compare <-----
|
Pass or Fail
Tests ausführen
Vollständige Überprüfung
Das Skript verify-all ist die abschließende Prüfstufe. Es führt statische Analyse, Prüfung der Codierungsstandards, PHPUnit-Tests und die Oracle-Regressionstestsuite aus:
./bin/verify-all
Dies muss erfolgreich sein, bevor eine Arbeit als abgeschlossen gilt.
Oracle-Regressionstestsuite
Alle 32 Szenarien ausführen:
./bin/test-regression
Eine einzelne Kategorie ausführen:
./bin/test-regression --category clean
./bin/test-regression --category conflict
./bin/test-regression --category identity
Die Kategorien sind die Verzeichnisnamen unter scenarios/: clean, conflict, identity, types, scale und edge. Bei einer unbekannten Kategorie wird No scenarios found ausgegeben und das Skript erfolgreich beendet. --category kann wiederholt werden, um mehrere Kategorien auszuführen.
Das Skript akzeptiert auch --jobs, ignoriert den Wert jedoch und führt die Szenarien nacheinander aus (#5).
Mit minimaler Ausgabe ausführen (nur Erfolg/Fehlschlag):
./bin/test-regression --fast
Einzelnes Szenario
Ein Szenario durch die gesamte Pipeline testen:
./bin/test-scenario column-level-clean
Einzelne Orakelschritte
./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
Unit-Tests
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 führt nur PHPUnit aus. Die PHPUnit-Suite unter oracle verweist auf tests/Oracle, wo sich die Szenario-Runner-Klassen, aber keine Testfälle befinden. Daher werden die 32 Szenarien nur über composer test:oracle, composer verify oder ./bin/verify-all ausgeführt.
Die MySQL-Integrationstests werden übersprungen, sofern MERQL_MYSQL_HOST, MERQL_MYSQL_DATABASE und MERQL_MYSQL_USER (sowie optional MERQL_MYSQL_PASSWORD und MERQL_MYSQL_PORT) nicht auf eine Testdatenbank verweisen.
Codequalität
composer cs # PSR-12 coding standards check
composer cs:fix # Auto-fix coding standards
composer analyse # PHPStan level 10 static analysis
Szenariokategorien
sauber (8 Szenarien)
Saubere Merges ohne Konflikte. Beide Seiten können Daten ändern, aber niemals dieselbe Spalte derselben Zeile.
| Szenario | Tests |
|---|---|
insert-only-theirs |
Sie haben Zeilen eingefügt, wir haben nichts geändert. |
update-only-theirs |
Sie haben Zeilen aktualisiert, wir haben nichts geändert. |
delete-only-theirs |
Sie haben Zeilen gelöscht, wir haben nichts geändert. |
insert-only-ours |
Wir haben Zeilen eingefügt, sie haben nichts geändert. |
mixed-no-overlap |
Beide haben unterschiedliche Tabellen oder Zeilen geändert. |
both-same-change |
Beide haben identische Änderungen vorgenommen (kein Konflikt). |
column-level-clean |
Beide haben dieselbe Zeile, aber unterschiedliche Spalten geändert. |
multi-table |
Änderungen an mehreren Tabellen. |
Konflikt (6 Szenarien)
Szenarien, bei denen Konflikte auftreten müssen.
| Szenario | Tests |
|---|---|
both-update-same-column |
Beide haben dieselbe Spalte auf unterschiedliche Werte geändert. |
update-vs-delete |
Eine Seite hat dieselbe Zeile aktualisiert, die andere gelöscht. |
delete-vs-update |
Umgekehrte Richtung bei Aktualisieren gegenüber Löschen. |
both-insert-same-pk |
Beide haben eine Zeile mit demselben Primärschlüssel eingefügt. |
multiple-conflicts |
Mehrere Konflikte in einem Merge. |
partial-conflict |
Einige Spalten verursachen Konflikte, andere lassen sich problemlos zusammenführen. |
Identität (4 Szenarien)
Sonderfälle bei der Zeilenidentität.
| Szenario | Tests |
|---|---|
auto-increment |
Neue Zeilen haben in den Branches unterschiedliche IDs. |
natural-key |
Abgleich anhand eindeutiger Spalten statt des Primärschlüssels. |
composite-key |
Zusammengesetzter Primärschlüssel. |
no-key |
Tabelle ohne Primärschlüssel (Fallback auf Inhalts-Hash). |
Typen (6 Szenarien)
Verarbeitung aller Spaltendatentypen.
| Szenario | Tests |
|---|---|
text-columns |
VARCHAR-, TEXT- und LONGTEXT-Werte. |
numeric-columns |
INT-, DECIMAL- und FLOAT-Werte. |
date-columns |
DATE-, DATETIME- und TIMESTAMP-Werte. |
json-columns |
Zusammenführen von JSON-Spalten. |
blob-columns |
Binärdaten. |
null-handling |
Übergänge von NULL zu einem Wert und von einem Wert zu NULL. |
Skalierung (4 Szenarien)
Leistung unter Last.
| Szenario | Tests |
|---|---|
1k-rows |
1,000 Zeilen. |
10k-rows |
10,000 Zeilen. |
100k-rows |
100,000 Zeilen. |
wide-table |
Tabelle mit 50+ Spalten. |
Randfälle (4 Szenarien)
Randfälle und Grenzbedingungen.
| Szenario | Tests |
|---|---|
empty-changeset |
Keine Änderungen auf einer oder beiden Seiten. |
schema-mismatch |
Spalte zwischen Snapshots hinzugefügt oder entfernt. |
encoding |
UTF-8, Emoji und Sonderzeichen. |
large-text |
Sehr große TEXT-/LONGTEXT-Werte. |
Szenarioaufbau
Jedes Szenario liegt als Verzeichnis unter scenarios/<category>/<name>/ und enthält eine Konfiguration scenario.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"
}
}
Bei Konfliktszenarien werden die erwarteten Konflikte genau angegeben:
{
"expectations": {
"conflicts": 1,
"conflict_details": [
{
"table": "test_posts",
"primary_key": {"id": 42},
"column": "title",
"ours_value": "Welcome",
"theirs_value": "Greetings"
}
]
}
}
Die ScenarioRunner-Pipeline
ScenarioRunner::run() steuert ein einzelnes Szenario:
OracleCapture::compute()erstellt Snapshots der drei Zustände anhand der Daten in JSON und führt den Merge aus.ScenarioComparator::compare()vergleicht das Merge-Ergebnis mit den erwarteten Werten inscenario.json.- Gibt Erfolg oder Fehlschlag mit einer Liste von Fehlermeldungen zurück.
use Merql\Tests\Oracle\ScenarioRunner;
$result = ScenarioRunner::run($scenario);
// ['name' => 'column-level-clean', 'pass' => true, 'failures' => []]
ScenarioRunner::runAll() führt alle Szenarien der Reihe nach aus:
$results = ScenarioRunner::runAll($scenarios);
Neues Szenario hinzufügen
- Erstelle ein Verzeichnis:
scenarios/<category>/<name>/. - Erstelle
scenario.jsonmit den Szenariometadaten und Erwartungen. - Definiere die drei Datenbankzustände in der Konfiguration JSON: Basisdaten, unsere Änderungen, ihre Änderungen.
- Führe das Szenario aus:
./bin/test-scenario <name>. - Überprüfe die gesamte Testsuite:
./bin/verify-all.
Codequalitätsstandards
merql setzt Folgendes voraus:
- PHPStan-Stufe 10 -- strenge statische Analyse ohne Ausnahmen in der Baseline.
- PSR-12 -- Codierungsstandards, geprüft von
phpcs. - Oracle-Regression -- alle 32 Szenarien müssen erfolgreich sein.
- Unit-Tests -- isolierte Tests für jede Komponente.
Das Skript verify-all führt alle vier Prüfungen nacheinander aus. Eine teilweise Freigabe wird nicht akzeptiert.
Kontinuierliche Integration
CI führt bei jedem Push und Pull Request zu main Codierungsstandards, PHPStan, composer test, die Integrationssuite mit MySQL 8.4 über mysqli, Tests der Merge-Grundfunktionen und composer audit aus. Das Oracle-Regressionstestkorpus wird nicht ausgeführt (#6). Führe daher lokal composer test:oracle oder ./bin/verify-all aus, bevor du dich auf eine Änderung verlässt.