Alguns meses atrás, eu escrevi um artigo sobre a sincronização de testes de integração de várias threads, que foi republicado em DZone Javalobby de onde recebi o comentário de Robert Saulnier, que salientou muito bem que você também pode usar join() para sincronizar uma thread e seus testes unitários. Isso me fez pensar em quantas maneiras você pode sincronizar os testes de integração multithreaded? Então, eu comecei a contar… e propus:
- Utilizar um atraso aleatório.
- Adicionar um CountDownLatch
- Thread.join()
- Adquirir um Semaphore
- Com um Future e ExecutorService
Agora, eu não vou explicar tudo a seguir detalhadamente, eu vou deixar o código falar por si, exceto para dizer que todas as amostras de código fazem mais ou menos a mesma coisa: o teste unitário cria uma instância ThreadWrapper e chama o seu método doWork() (ou faz uma call() no caso de Future). A principal thread do teste unitário aguarda a thread concluir antes de afirmar que o teste passou.
Para o código de exemplo que demonstra os pontos 1 e 2, dê uma olhada no meu artigo original, em Sincronização de testes de integração multithread, embora eu não recomende o ponto 1: utilizar um atraso aleatório.
Thread.join()
[java]
public class ThreadWrapper {
private Thread thread;
/**
* Start the thread running so that it does some work.
*/
public void doWork() {
thread = new Thread() {
/**
* Run method adding data to a fictitious database
*/
@Override
public void run() {
System.out.println("Start of the thread");
addDataToDB();
System.out.println("End of the thread method");
}
private void addDataToDB() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
System.out.println("Off and running…");
}
/**
* Synchronization method.
*/
public void join() {
try {
thread.join();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
[/java]
[java]
public class ThreadWrapperTest {
@Test
public void testDoWork() throws InterruptedException {
ThreadWrapper instance = new ThreadWrapper();
instance.doWork();
instance.join();
boolean result = getResultFromDatabase();
assertTrue(result);
}
/**
* Dummy database method – just return true
*/
private boolean getResultFromDatabase() {
return true;
}
}
[/java]
Adquirindo um Semaphore
[java]
public class ThreadWrapper {
/**
* Start the thread running so that it does some work.
*/
public void doWork() {
doWork(null);
}
@VisibleForTesting
void doWork(final Semaphore semaphore) {
Thread thread = new Thread() {
/**
* Run method adding data to a fictitious database
*/
@Override
public void run() {
System.out.println("Start of the thread");
addDataToDB();
System.out.println("End of the thread method");
semaphore.release();
}
private void addDataToDB() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
aquire(semaphore);
thread.start();
System.out.println("Off and running…");
}
private void aquire(Semaphore semaphore) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
[/java]
[java]
public class ThreadWrapperTest {
@Test
public void testDoWork() throws InterruptedException {
ThreadWrapper instance = new ThreadWrapper();
Semaphore semaphore = new Semaphore(1);
instance.doWork(semaphore);
semaphore.acquire();
boolean result = getResultFromDatabase();
assertTrue(result);
}
/**
* Dummy database method – just return true
*/
private boolean getResultFromDatabase() {
return true;
}
}
[/java]
Com um futuro
[java]
public class ThreadWrapper implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
System.out.println("Start of the thread");
Boolean added = addDataToDB();
System.out.println("End of the thread method");
return added;
}
/**
* Add to the DB and return true if added okay
*/
private Boolean addDataToDB() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Boolean.valueOf(true);
}
}
[/java]
[java]
public class ThreadWrapperTest {
@Test
public void testCall() throws ExecutionException, InterruptedException {
ThreadWrapper instance = new ThreadWrapper();
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<Boolean> future = executorService.submit(instance);
Boolean result = future.get();
assertTrue(result);
}
}
[/java]
Depois de ter listado todos esses métodos, a próxima coisa a considerar é qual é o melhor? Ao fazer essa pergunta, você tem que definir a palavra “melhor” em termos de melhor para quê? Melhor para simplicidade? Sustentabilidade? Velocidade ou o tamanho do código? Eu prefiro o uso de um CountDownLatch. Thread.join() é um pouco antigo; lembre-se de que Semaphore e CountDownLatch foram escritos para melhorar as técnicas originais de segmentação Java. ExecutorService parece um pouco peso pesado para o que precisamos aqui. No final do dia, a escolha da técnica realmente se resume à preferência pessoal.
***
Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://www.captaindebug.com/2013/04/five-ways-of-synchronising.html#.UdR8bPlwrng