Хабрахабр

[Из песочницы] Неочевидная проблема использования assert

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

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

TransactionRunner.run(dbDataManager(), new MethodTransaction() );

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

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.

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

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть