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.