Em um projeto que participo, há um conjunto de EJBs stateless reponsáveis por atender chamadas. Foi preciso disponibilizar para todas as camadas um contexto de mensagens cujo escopo é uma chamada ao serviço. Como não seria possível usufruir de um bean com escopo de Request, que exigiria trabalhar com beans stateful, simulei esse contexto através de ThreadLocal. Conforme a teoria, esse recurso “provides thread-local variables (…) each thread has its own, independently initialized copy of the variable.”
Por ser um ponto crítico do sistema, desejei criar um teste automatizado para garantir o comportamento ao longo das mudanças. Em linhas gerais, o teste deveria criar várias threads utilizando seus contextos de mensagens e certificar que eles não se sobrepusessem. Durante a prospecção soube que o framework TesteNG fornece a opção de testes com múltiplas threads, mas julguei que seria um teste muito simples para precisar de uma biblioteca adicional.
Dessa forma, meu teste com JUnit da seguinte forma:
import org.junit.Assert; import org.junit.Test; public class ContextoTest { private static final int QTD_THREADS = 1000; @Test public void contextoDeveSerExclusivoDaThread() throws InterruptedException { Thread[] threads = new Thread[QTD_THREADS]; // Cria e roda as threads for (int i = 0; i < QTD_THREADS; i++) { threads[i] = new ThreadTeste(); threads[i].start(); } // Aguarda a execução de todas das threads for (Thread t : threads) { t.join(); } } class ThreadTeste extends Thread { @Override public void run() { Contexto.inicializar(); // Dorme por um tempo aleatório para forçar paralelismo esperarUmTempoAleatorio(); // Adiciona o nome da thread corrente no contexto Contexto.adicionarMensagem(Thread.currentThread().getName()); // Contexto deve ter apenas 1 mensagem, contendo nome da thread Assert.assertEquals(1, Contexto.getMensagens().size()); Assert.assertEquals(Thread.currentThread().getName(), Contexto.getMensagens().get(0)); } private void esperarUmTempoAleatorio() { try { Thread.sleep((long) (1000 * Math.random())); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Um ponto interessante é introduzir algum grau de desordem no paralelismo, oque foi feito fazendo a thread dormir por um tempo aleatório antes de realizar o teste. Além disso, é primordial fazer join() das threads criadas ao fim do teste unitário para certificar que o JUnit não prosseguirá para outros testes enquanto ainda existem threads rodando do teste atual.