Подтвердить что ты не робот

Как unit test одновременное чтение/запись с помощью PHPUnit?

Недавно я столкнулся с проблемой в реальном приложении. Я понял, что у меня есть все больше и больше concurrency исключений и блокировок с базой данных.

В основном я запускаю транзакцию, для которой требуется зафиксировать SELECT и INSERT в той же таблице.

Но поскольку загрузка действительно тяжелая, каждая транзакция блокирует таблицу, в большинстве случаев она так быстро не вызывает никаких проблем, но есть точка, где блокировки начинают все больше и больше ждать.

Я смог немного исправить эту проблему, настроив запросы.

Хотя, теперь, я бы хотел написать несколько тестов с PHPUnit, чтобы проверить мое исправление и избежать любых регрессий.

Я не смог найти никаких материалов о том, как это сделать.

Поскольку PHP не многопоточен, у меня нет идей, как я могу запускать параллельные запросы в одном тесте для проверки.

В принципе, я хотел бы иметь возможность запускать несколько вызовов в одном тесте, чтобы все было в порядке.

Я знаю, что могу попытаться выполнить некоторые тесты высокого уровня, напрямую обратившись к серверу http и загрузив все приложение, но поскольку моя проблема возникает из автономной библиотеки, я бы предпочел проверить ее на себя.

Любые идеи?

4b9b3361

Ответ 1

Короткий ответ: нет хорошего способа проверить параллельные чтения/записи в реальной базе данных с помощью PHPUnit. Это просто не правильный инструмент для этой работы.

Но есть несколько аспектов для хорошего решения для тестирования этого. Сначала код может быть написан для обработки всех возможных сценариев. Система баз данных, такая как Postgres, немедленно сработает при блокировках и проблемах с транзакциями. Чтобы обработать его элегантно, я использую код, который выглядит примерно так (псевдокод, также используемый для ответа на еще один вопрос):

begin transaction
while not successful and count < 5
    try 
        execute sql
        commit
    except
        if error code is '40P01' or '55P03'
            # Deadlock or lock not available
            sleep a random time (200 ms to 1 sec) * number of retries
        else if error code is '40001' or '25P02'
            # "In failed sql transaction" or serialized transaction failure
            rollback
            sleep a random time (200 ms to 1 sec) * number of retries
            begin transaction
        else if error message is 'There is no active transaction'
            sleep a random time (200 ms to 1 sec) * number of retries
            begin transaction
    increment count

Затем создайте два набора тестов: один набор должен подтвердить, что код правильно обрабатывает ситуации (т.е. модульные тесты). Другой набор тестов предназначен для среды (т.е. Интеграции/функциональных тестов).

Тесты устройств

Я считаю, что это сложная ситуация для воспроизведения в тесте PHPUnit, который подключается к базе данных, а использование реальной базы данных не подходит для истинного unit test. Вместо этого создайте заглушки PDO и модульные тесты, которые генерируют все исключения базы данных. Это подтверждает, что код работает как ожидалось, но не тестирует concurrency на любой реальной базе данных, например:

$iterationCount = 0;
$db->runInTransaction(function() use (&$iterationCount) {
    $iterationCount++;
    if ($iterationCount === 1) {
        $exception = new PDOExceptionStub('Deadlock');
        $exception->setCode('40P01');
        throw $exception;
    }
});

// First time fails, second time succeeds
$this->assertEquals(2, $iterationCount, 'Expected 2 iterations of runInTransaction');

Напишите полный набор тестов, которые не подключаются к БД, но подтвердите логику.

Интеграционные тесты

Как вы уже нашли, PHPUnit - это просто не лучший инструмент для выполнения теста нагрузки. Это не подходит для чего-либо более сложного, чем последовательные модульные и интеграционные тесты. Вы можете одновременно запускать несколько экземпляров PHPUnit, чтобы увеличить нагрузку на базу данных. Однако я считаю, что это выходит за рамки того, для чего это предназначалось, плюс это не поможет вам контролировать базу данных по вопросам. Поэтому я не вижу возможности избежать тестов более высокого уровня, которые вы хотите избежать.

Но ваша библиотека может быть протестирована без запуска вашего полного приложения. Я бы создал самое простое приложение для его тестирования. Он может иметь один или несколько сценариев CLI, которые подключаются к базе данных. Эти сценарии могут появляться несколько раз, чтобы загрузить нагрузку на базу данных. Если это помогает, это также позволяет вам иметь приложение с дополнительным кодом для мониторинга самой базы данных.