Back-End

16 ago, 2013

Cinco maneiras de sincronizar testes de integração multithreaded

Publicidade

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:

  1. Utilizar um atraso aleatório.
  2. Adicionar um CountDownLatch
  3. Thread.join()
  4. Adquirir um Semaphore
  5. 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