Os testes de unidade são uma parte importante de qualquer projeto grande o suficiente. Quero compartilhar com vocês uma pequena história de detetive relacionada à queda em massa não óbvia.
Começa com o fato de que, como resultado de uma confirmação inofensiva, cerca de 150 testes caíram no projeto, enquanto o conjunto de testes em queda não era estável. Os testes não foram interconectados, os testes foram realizados sequencialmente. O banco de dados h2 na memória é usado como fonte de dados para testes. A queda da grande maioria desses 150 testes foi acompanhada por um erro no log: "Não é possível obter uma conexão, erro de pool Tempo limite aguardando objeto inativo". Deve-se dizer que o tamanho do conjunto de conexões ao executar testes no projeto é 1.
Uma pequena digressão lírica: no código do projeto, a transação é desconectada do fluxo periodicamente, depois o código é executado em uma transação separada e, finalmente, a transação é vinculada novamente. Para esses casos, uma classe auxiliar foi escrita, cujo uso se parece com isso:
TransactionRunner.run(dbDataManager(), new MethodTransaction() { @Override public ExecutionResult runInTransaction() throws Exception {
Como resultado da análise, foi revelado que o erro começa a aparecer após um teste com falha que contém uma chamada de código em uma transação:
TransactionRunner.run(dbDataManager(), new MethodTransaction() { @Override public ExecutionResult runInTransaction() throws Exception {
Dê uma olhada na classe TransactionRunner, uma chamada de método leva ao seguinte código:
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); } }
Então, qual é o problema aqui? E o problema é que o AssertionError que surge como resultado da execução do código de teste não é herdado de Exception, o que significa que a transação aninhada não é revertida nem confirmada. Como o tamanho do pool de conexões é igual a um, obtemos o mesmo erro "Não é possível obter uma conexão, tempo limite do erro do pool aguardando objeto inativo" ao tentar obter o objeto Connection pelos testes subsequentes.
Moral: é necessário colocar afirmações em testes com cautela e, no caso de falhas não óbvias e, em particular, em massa, uma das soluções é verificar se o tratamento de exceções leva em consideração objetos que não são herdados de Exception.
O caso me pareceu digno de fixação, talvez essa experiência seja útil para alguém.