Стратегия тестирования
В merql используется модель тестирования по эталонным результатам. В каждом тестовом сценарии задан известный набор входных данных (три состояния базы данных) и детерминированный ожидаемый результат (эталон), с которым сравнивается фактический результат слияния. 32 сценария охватывают слияния без конфликтов, конфликты, определение идентификаторов, типы данных, масштаб и граничные случаи.
# Run the full verification suite.
./bin/verify-all
Команды на этой странице нужно запускать из клона репозитория merql после composer install. Скрипты разработки в bin/, набор данных scenarios/ и тесты не входят в пакет Composer. Среди зависимостей для разработки есть PHPUnit 13, которому требуется PHP 8.4 или новее, хотя сама библиотека работает на PHP 8.2.
Тестирование по эталонным результатам
Поскольку не существует готового эталонного инструмента для трехстороннего слияния баз данных, ожидаемый результат вычисляется по известным входным данным, а не формируется внешним инструментом. Для каждого сценария задан детерминированный ожидаемый результат, который можно проверить логически:
- Подготовка -- создайте три состояния базы данных (база, наш вариант, их вариант) с известным содержимым.
- Эталон -- вычислите ожидаемый результат слияния по известным входным данным.
- Фактический результат -- выполните слияние трех состояний с помощью merql.
- Сравнение -- фактический результат должен точно совпасть с ожидаемым.
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 |
Другая сторона добавила строки, наша сторона ничего не меняла. |
update-only-theirs |
Другая сторона обновила строки, наша сторона ничего не меняла. |
delete-only-theirs |
Другая сторона удалила строки, наша сторона ничего не меняла. |
insert-only-ours |
Наша сторона добавила строки, другая сторона ничего не меняла. |
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 |
Сопоставление по уникальным столбцам вместо 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:
{
"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"
}
}
Для сценариев с конфликтами ожидаемые конфликты указаны подробно:
{
"expectations": {
"conflicts": 1,
"conflict_details": [
{
"table": "test_posts",
"primary_key": {"id": 42},
"column": "title",
"ours_value": "Welcome",
"theirs_value": "Greetings"
}
]
}
}
Конвейер ScenarioRunner
ScenarioRunner::run() управляет выполнением одного сценария:
OracleCapture::compute()создаёт три снимка на основе данных JSON сценария и выполняет слияние.ScenarioComparator::compare()сверяет результат слияния с ожидаемыми значениями вscenario.json.- Возвращает результат: пройдено или не пройдено, а также список сообщений об ошибках.
use Merql\Tests\Oracle\ScenarioRunner;
$result = ScenarioRunner::run($scenario);
// ['name' => 'column-level-clean', 'pass' => true, 'failures' => []]
ScenarioRunner::runAll() последовательно запускает каждый сценарий:
$results = ScenarioRunner::runAll($scenarios);
Добавление нового сценария
- Создайте каталог:
scenarios/<category>/<name>/. - Создайте
scenario.jsonс метаданными сценария и ожидаемыми результатами. - Определите три состояния базы данных в конфигурации JSON: исходные данные, изменения нашей стороны, изменения другой стороны.
- Запустите сценарий:
./bin/test-scenario <name>. - Проверьте весь набор тестов:
./bin/verify-all.
Стандарты качества кода
merql обеспечивает:
- Уровень PHPStan 10 -- строгий статический анализ без исключений на основе базового набора.
- PSR-12 -- соблюдение стандартов кодирования проверяет
phpcs. - Регрессионные проверки оракула -- все 32 сценария должны пройти.
- Модульные тесты -- изолированные проверки каждого компонента.
Скрипт verify-all последовательно выполняет все четыре проверки. Частичное подтверждение не принимается.
Непрерывная интеграция
При каждом отправлении изменений и запросе на слияние в main CI запускает проверку стандартов кодирования, PHPStan, composer test, набор интеграционных тестов на MySQL 8.4 через mysqli, проверку покрытия примитивов слияния и composer audit. Регрессионный набор сценариев оракула не запускается (#6), поэтому перед тем, как полагаться на изменение, локально запустите composer test:oracle или ./bin/verify-all.