Teste unitário com múltiplas threads no JUnit

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.