Неочевидная проблема использования assert

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

Начинается она с того, что в проекте в результате определенного безобидного коммита упало порядка 150 тестов, набор падающих тестов при этом не являлся стабильным. Тесты не были связаны между собой, выполнение тестов происходило последовательно. В качестве источника данных для тестов служит in-memory база данных h2. Падение подавляющего большинства из этих 150 тестов сопровождалось ошибкой в логе: «Cannot get a connection, pool error Timeout waiting for idle object». Следует сказать, что размер пула коннектов при выполнении тестов в проекте равен 1.

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

TransactionRunner.run(dbDataManager(), new MethodTransaction() {            @Override            public ExecutionResult runInTransaction() throws Exception {                 // код, который необходимо выполнить в отдельной транзакции                return result;           }  );

В результате анализа было выявлено, что ошибка начинает проявляться после провалившегося теста, содержащего в себе вызов кода в транзакции:

TransactionRunner.run(dbDataManager(), new MethodTransaction() {            @Override            public ExecutionResult runInTransaction() throws Exception {                 // ... рабочий код                 // assert который валится                assert( 1, result.getSomeNotEqualOneIntValue() );                return result;           }  );

Заглянем внутрь класса TransactionRunner, вызов метода приводит к следующему коду:

protected ExecutionResult run() throws CommonException {         Transaction outerTr = getThreadTransaction();         bindThreadTransaction(null);         try {             beginTransaction();             try {                 setResult(transactionCode.runInTransaction());             } catch (Exception e) {                 dbDataManager().rollbackTransaction();                 if (transaction.onException(this, e))                     throw e;             }               dbDataManager().commitTransaction();              return getResult();         } catch (Exception e) {             throw ExceptionUtil.createCommonException(e);         } finally {             bindThreadTransaction(outerTr);         }     }

Итак, в чем же здесь проблема? А проблема заключается в том, что AssertionError возникающий в результате выполнения кода теста не наследуется от Exception, а значит вложенная транзакция ни откатывается, ни коммитится. Так как размер пула коннектов равен единице — получаем ту самую ошибку «Cannot get a connection, pool error Timeout waiting for idle object» при попытке получить объект Connection последующими тестами.

Мораль: необходимо размещать assert’ы в тестах с осторожностью, а в случае неочевидных и, в особенности, массовых падений одним из вариантов решения является проверка, учитывает ли обработка исключений объекты не наследующиеся от Exception.

Случай показался мне достойным фиксации, возможно кому-нибудь пригодится этот опыт.

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *