Posts com a tag ‘junit’

Testes unitários com EasyMock e JUnit – Introdução

13 de abril de 2010

A partir de agora, o Blog do Seu Enium terá a participação de um novo colaborador. Seu nome é Leandro, mais conhecido como Mendigo, e trabalhamos juntos no UOL.

Para iniciar a participação dele no blog, ele vai falar um pouco sobre uma biblioteca que auxilia bastante na hora de efetuar testes unitários com java: EasyMock.

Esta biblioteca permite que nós criemos objetos de um determinado tipo (classe ou interface), para podermos isolar a classe a ser testada.

Mas por que fazer isso? Bom, quando estamos testando de forma unitária, é interessante que foquemos somente na classe, ou mesmo somente em um método da classe. Porém, em um projeto orientado à objetos, o que temos, idealmente, é um conjunto de classes efetuando tarefas específicas e interagindo entre si. A grande maioria das classes possui dependências em relação à outras classes. É importante que o grau de dependência (ou acoplamento) seja baixo, para que alterações em dependências não afetem drasticamente a classe dependente, mas esse acoplamento dificilmente será zero (nesse caso teríamos uma classe inchada, fazendo muita coisa, e difícil de manter).

Por exemplo, vamos considerar uma classe que leia informações de uma fonte de entrada, e escreva informações em um destino. Além disso, essa classe é capaz de interpretar alguns comandos básicos para manipular os dados de entrada (neste primeiro exemplo a classe sabe somente enviar a hora atual).

(o código do exemplo está disponível aqui)

Processor

package foo;
 
public class Processor
{
 
   private Source source;
   private Destination destination;
 
   public Processor( Source source, Destination destination ) {
      this.source = source;
      this.destination = destination;
   }
 
   public void process() {
      String input = null;
      try
      {
         input = source.read();
      }
      catch (Exception e)
      {
 
      }
 
      if( input != null ) {
         String output = transform( input );
 
         try
         {
            destination.write( output );
         }
         catch (Exception e)
         {
            throw new ProcessorException("Nao foi possível enviar os dados");
         }
      }
   }
 
   private String transform(String input)
   {
      if( "[[Time]]".equals( input ) ) {
         return "time: " + System.currentTimeMillis();
      }
      return input;
   }
}

Source

package foo;
 
public interface Source
{
 
   String read();
 
}

Destination

package foo;
 
public interface Destination
{
 
   void write(String output);
 
}

Para complicar um pouco, vamos considerar que Source obtém os dados de um banco de dados e que Destination envia os dados escritos pela rede. Dessa foram para testarmos a classe Processor, seria necessário acessar um banco de dados e enviar dados pela rede.

Em testes de integração ou de sistema, isso está OK, mas em testes de unidade não. Estes deveriam ser isolados, contidos, não condicionados à disponibilidade ou não dos recursos externos como rede ou banco. Além disso, eles deveriam rodar de forma muito rápida, para não atrasar o processo de build e de desenvolvimento.

Com o EasyMock podemos criar um objeto que se passe por um Source, mas que não acessa realmente o banco de dados, e um outro que emule o comportamento de um Destination, sem efetivamente enviar dados pela rede, isolando assim a nossa “unidade” (Processor) e tendo um controle total sobre os mocks (Neste post estou chamando tudo de mock, porém há algumas implicações “técnicas” nesta nomenclatura, e para quem quiser se aprofundar melhor, sugiro este texto: http://martinfowler.com/articles/mocksArentStubs.html)

Vamos escrever um teste para o fluxo mais básico do nosso Processor.

package foo;
 
import org.easymock.EasyMock;
import org.junit.Test;
 
public class ProcessorTest
{
 
   @Test
   public void testProcess()
   {
      // Criação dos mocks e configurando o comportamento esperado
      Source source = EasyMock.createMock(Source.class);
      Destination destination = EasyMock.createMock(Destination.class);
 
      EasyMock.expect(source.read()).andReturn("SeuEnium");
      destination.write("SeuEnium");
 
      EasyMock.replay(source, destination);
 
      // Testando a nossa "unidade"
      Processor processor = new Processor(source, destination);
 
      processor.process();
 
      // Verificando o comportamento
      EasyMock.verify(source, destination);
   }
 
}

Neste teste podemos ver um esquema básico de utilização de mocks:

    1. Criação dos mocks – a forma mais simples é a ilustrada no exemplo: EasyMock.createMock( Source.class )
      Este método irá criar um mock do tipo especificado (Classe ou Interface) – O EasyMock é capaz de criar mocks tanto de classes quanto de interfaces (neste exemplo vamos lidar somente com interfaces)
      Comportamento esperado – os mocks são criados em um estado de gravação – tudo o que for feito com o objeto até a chamada de EasyMock.replay( source ) será gravado internamente pelo EasyMock, e esse comportamento será depois validado.
      Como podemos ver no exemplo, utilizando EasyMock.expect( source.read() ) estamos dizendo ao EasyMock que é esperada uma chamada ao método read do mock, e que esta chamada deve retornar a String “SeuEnium”. Porém, como podemos ver em destination.write("SeuEnium"), não é necessário utilizar EasyMock.expect para mockar uma chamada (neste caso não é nem possível, pois o método write é void, e ocorrerá um erro de compilação caso se tente mockar write utilizando EasyMock.expect). O uso de EasyMock.expect se faz necessário quando queremos especificar um valor de retorno para o método mockado.
      Para indicar ao EasyMock que já configuramos o comportamento do mock e que a partir deste momento o objeto deve se comportar realmente como um objeto “normal”, utilizamos EasyMock.replay( source, destination ). A partir deste ponto, não é mais possível efetuar chamadas a EasyMock.expect, a não ser que se utilize EasyMock.reset( source ).
      Execução do teste da “unidade” – aqui vamos fazer o teste propriamente dito e efetuar quaisquer verificações de resultado da unidade testada.
      Verificação do comportamentoEasyMock.verify( source, destination ) diz ao EasyMock para verificar se os métodos esperados foram chamados – se o comportamento esperado não for satisfeito, o EasyMock irá disparar um exceção, fazendo o teste unitário falhar.

O EasyMock oferece uma grande flexibilidade na especificação do comportamento dos mocks, o que permite que sejam testados muitos cenários que de outro modo seriam difíceis de testar – por exemplo, vamos supor que queremos verificar que nosso Processor se comporte de uma determinada maneira quando ocorrer um problema ao ler do banco de dados. Podemos dizer ao EasyMock para disparar uma exceção no momento de leitura, e verificar o comportamento do Processor quando isso acontece.

   @Test
   public void testProcessWhenReadFails() {
      // Criação dos mocks e configurando o comportamento esperado
      Source source = EasyMock.createMock(Source.class);
      Destination destination = EasyMock.createMock(Destination.class);
 
      // Especificando o disparo de uma exceção
      EasyMock.expect(source.read()).andThrow(new RuntimeException("Erro na leitura"));
 
      EasyMock.replay(source, destination);
 
      // Testando a nossa "unidade"
      Processor processor = new Processor( source, destination );
 
      processor.process();
 
      // Verificando o comportamento
      EasyMock.verify(source, destination);
   }

Neste código estamos testando que Processor nunca irá tentar escrever os dados pela rede quando houver uma exceção na leitura dos dados – Como não configuramos nenhum comportamento para destination, qualquer chamada a métodos desse objeto irá disparar uma exceção.

Digamos agora que gostaríamos de testar o comportamento de Processor quando ocorrer um erro ao escrever pela rede. Neste caso, gostaríamos que uma exceção fosse lançada pelo próprio Processor, para notificar o erro.

   @Test(expected=ProcessorException.class)
   public void testProcessWhenWriteFails() {
      // Criação dos mocks e configurando o comportamento esperado
      Source source = EasyMock.createMock(Source.class);
      Destination destination = EasyMock.createMock(Destination.class);
 
      // Especificando o disparo de uma exceção
      EasyMock.expect(source.read()).andReturn("SeuEnium");
      destination.write("SeuEnium");
      EasyMock.expectLastCall().andThrow( new RuntimeException("Erro na escrita") );
 
      EasyMock.replay(source, destination);
 
      // Testando a nossa "unidade"
      Processor processor = new Processor( source, destination );
 
      processor.process();
   }

Um outro ponto em que o EasyMock oferece flexibilidade é na verificação dos parâmetros passados para as chamadas ao mock. Por padrão, o EasyMock irá utilizar o equals() para verificar se o parâmetro fornecido é o mesmo que foi configurado como esperado. Porém, algumas vezes esse comportamento pode ser restritivo, e o EasyMock oferece formas para mudarmos a maneira como ele irá verificar os parâmetros.

Por exemplo, o no processor sabe interpretar o comando “[[Time]]”. Quando essa String é recebida, o processor envia pela rede o tempo em milissegundos atual. Da maneira como está implementado, o tempo enviado irá ser diferente a cada execução do teste. Há diversas maneiras de testar este tipo de comportamento, e a que vamos utilizar aqui não é a melhor delas, mas ela ilustra o conceito. Vamos utilizar um regexp para validar se foi enviado o tempo para o destino.

   @Test
   public void testProcess2() {
      // Criação dos mocks e configurando o comportamento esperado
      Source source = EasyMock.createMock(Source.class);
      Destination destination = EasyMock.createMock(Destination.class);
 
      // Especificando o disparo de uma exceção
      EasyMock.expect(source.read()).andReturn("[[Time]]");
      destination.write( EasyMock.matches( "time: \\d+" ) );
 
      EasyMock.replay(source, destination);
 
      // Testando a nossa "unidade"
      Processor processor = new Processor( source, destination );
 
      processor.process();
   }

Bom pessoal, essa foi uma introdução ao uso do EasyMock para facilitar testes de unidade. No próximo post, vamos dar uma olhada em um exemplo um pouco menos vago, e vamos ver como efetuar mock de classes realmente, e não somente de interfaces.

Um abraço.

Capturando a tela com o Selenium e Junit

4 de janeiro de 2010

Uma das coisas que notei usando Selenium é que as vezes você fica perdido quando um teste falha.
Nem sempre a mensagem de erro retornada pelo Junit ou pelo Selenium é suficiente para entender o porquê de um teste falhar.
A imagem do browser na hora em que houver uma falha pode te auxiliar na detecção do verdadeiro erro do seu teste ou sistema.

O Selenium possuí 2 comandos para dar screenshot:

1- captureScreenshot(String fileName)

Esse comando captura toda a tela. É como se você desse um print screen com o teclado.
Ficaria mais ou menos assim:

Tela Inteira

2- captureEntirePageScreenshot (String filename, String kwargs);

Esse comando captura toda a página que está no browser.
A imagem ficaria assim:

Página Inteira

O benefício do captureScreenshot é que funciona em todos os browsers. Em compensacão, se sua página é muito grande, ou seja, você precisa usar a barra de rolagem para ver a página inteira, a imagem gerada apenas conterá a parte visível. Com o captureEntirePageScreenshot esse problema não acontece, mesmo que a página tenha scroll, a imagem final será da página completa. O contra do captureEntirePageScreenshot é que ele só funciona nativamente no Firefox. Para funcionar no IE, você precisa instalar uma ferramenta http://snapsie.sourceforge.net.

Agora que você já sabe qual o comando para tirar screenshot com o Selenium, vamos agora aprender a tirar o screenshot apenas quando seu teste em Junit der erro.
Para isso você vai ter que usar a versão 4.7 ou mais recente do Junit.

Primeiro você precisa criar uma classe que implemente a interface MethodRule. Depois você precisa implementar o método apply com a lógica de chamar o comando de capturar a tela. A classe ficaria mais ou menos assim:

package br.com.seuenium;
 
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
 
import com.thoughtworks.selenium.Selenium;
 
public class ScreenshotRule implements MethodRule {
 
  public Statement apply(final Statement statement, 
               final FrameworkMethod frameworkMethod, final Object testCase) {
    return new Statement() {
 
      public void evaluate() throws Throwable {
        try {
          statement.evaluate();
        } catch (Throwable e) {
 
          try {
            ExemploTest exemploTest = (ExemploTest) testCase;
            Selenium selenium = exemploTest.getSelenium();
            if(selenium != null) {
              String fileName = "/tmp/image.png";
              selenium.captureEntirePageScreenshot(fileName, "");
            }
 
          } catch (Exception e2) {
            System.err.println("Não foi possível capturar o screenshot");
          }
 
          throw e;
        }
      }
 
    };
  }
 
}

Esse código chama o método evaluate do statement que corresponde à execução de um método anotado com @Test. Se retornar uma exceção, o comando de capturar a tela é acionado.

Em seguida, na sua classe de teste, você deve criar um atributo público do tipo ScreenshotRule e deve inicializá-lo na própria declaração e anotá-lo com @Rule.
Deve ficar mais ou menos assim:

package br.com.seuenium;
 
import junit.framework.Assert;
 
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
 
import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
 
public class ExemploTest {
 
  private Selenium selenium;
 
  @Rule
  public ScreenshotRule screenshotRule = new ScreenshotRule();
 
  @Before
  public void start(){
    selenium = new DefaultSelenium("localhost", 4444, "*firefox", "http://www.seuenium.com.br");
    selenium.start();
  }
 
  @After
  public void stop(){
    selenium.stop();
  }
 
  @Test
  public void testErro() {
    selenium.open("/testes/7-screenshot/teste.php");
    Assert.assertEquals("Hello World", selenium.getText("id=inexistente"));
  }
 
  @Test
  public void testSucesso() {
    selenium.open("/testes/7-screenshot/teste.php");
    Assert.assertEquals("Hello World", selenium.getText("id=existente"));
  }
 
  public Selenium getSelenium() {
    return selenium;
  }
 
}

Eu criei um projeto maven pronto para ser executado. Basta fazer o download aqui.
Descompacte o arquivo, entre no diretório e execute “mvn clean install”. O testErro irá falhar e você poderá ver o screenshot em target/testErro.png.

Bom, é isso pessoal. Qualquer dúvida mandem um comentário.
Até a próxima.

Teste em paralelo com a nova versão do Junit 4.6

14 de abril de 2009

No post anterior mostrei que é possível paralelizar os testes com TestNG.

Para os que estão mais familiarizados com Junit, o TestNG pode parecer mais difícil, mas não se preocupe.

Hoje foi liberada a versão 4.6 do Junit e com ela você consegue paralelizar os seus testes.

Segue o exemplo no release notes de como rodar em paralelo:

public static class Example {
	@Test public void one() throws InterruptedException {
		Thread.sleep(1000);
	}
	@Test public void two() throws InterruptedException {
		Thread.sleep(1000);
	}
}
 
@Test public void testsRunInParallel() {
	long start= System.currentTimeMillis();
	Result result= JUnitCore.runClasses(ParallelComputer.methods(),
			Example.class);
	assertTrue(result.wasSuccessful());
	long end= System.currentTimeMillis();
	assertThat(end - start, betweenInclusive(1000, 1500));
}

Assim como o TestNG você pode escolher entre paralelizar os métodos ou a classe toda: ParallelComputer.methods() ou ParallelComputer.classes()

Para quem usa maven, a integração nativa da versão 4.6 do Junit com o Surefire Plugin funciona somente para testes sequenciais. Se você quiser paralelizar vai ter que seguir o exemplo acima do release notes.
Mas acredito que logo mais sairá uma versão do maven-surefire-plugin em que você apenas terá que colocar na configuração se você quer paralelizar, assim como ocorre com o TestNG, com o config no pom.xml:

<parallel>tests|methods</parallel>

Qualquer outra novidade volto a postar… :)

Inserido em 02/12/2009
*A versão do junit 4.6 estava bugada. Use uma mais recente, 4.7 ou 4.8.

Testes em paralelo com Selenium Grid, Maven e TestNG

5 de abril de 2009

O Selenium se provou ser uma ótima ferramenta para auxiliar os testes em sistemas web. O desenvolvedor ganha mais confiança ao realizar alterações no sistema, os webmasters também e se preocupam cada vez mais em deixar as telas mais fáceis para automatizar e dar manutenção.
Mas à medida que o número de testes aumenta, o tempo total de execução também aumenta, e muito. A maneira de conseguir diminuir esse tempo é usando o Selenium Grid.

O Selenium Grid atua como um proxy entre as requisições dos clientes e um pool de servidores Remote Control. Com isso, o grid consegue gerenciar ao mesmo tempo todos os RCs que estão conectados. O benefício disso tudo é que você pode paralelizar a execução dos seus testes e consequentemente o tempo de execução dos seus testes diminui.

E como você consegue fazer isso?

Primeiro você tem que baixar o Selenium Grid em http://selenium-grid.seleniumhq.org/download.html. A última versão é a 1.0.3.
Descompacte o arquivo baixado em algum lugar de sua preferência.

Você também vai ter que ter o ant instalado em sua máquina. Baixe-o aqui.

Entre no diretório do Selenium Grid e rode o comando para subir o servidor principal, o hub.

ant launch-hub

O hub vai subir na porta 4444 e é onde o seu teste tem que se conectar.

Agora suba dois servidores do Selenium RC com o browser Firefox:

ant -Dport=5556 -Denvironment=*chrome launch-remote-control
ant -Dport=5557 -Denvironment=*chrome launch-remote-control

Acesse agora http://localhost:4444/console e você verá uma tela contendo os servidores disponíveis.

Pronto, você já tem a estrutura de servidores para que seus testes rodem paralelamente.

Agora vamos criar um projeto Maven para que tudo fique organizado e seja fácil disparar os testes (ao fim do post tem um link para fazer download do projeto com o exemplo). Para baixar o Maven acesse aqui.

Rode o seguinte comando para criar um novo projeto de nome “testes-em-paralelo”:

mvn archetype:create -DgroupId=br.com.seuenium -DartifactId=testes-em-paralelo

Esse comando cria uma estrutura básica de diretórios para um projeto Java. Um desses diretórios criados é o src/test/java onde o Maven por convenção vai chamar todos os seus testes que estiverem localizados nessa pasta. Essa convenção é definida pelo plugin maven-surefire-plugin.
Esse plugin consegue rodar tanto testes em Junit como em TestNG.
Para quem não conhece, o TestNG é um framework de testes praticamente igual ao Junit, porém com algumas funcionalidades a mais. Uma dessas funcionalidades é a possibilidade de rodar os seus testes em paralelo.

Para habilitar o TestNG, apenas inclua a dependência da lib do TestNG no seu pom.xml. Vai ficar assim:

<dependency>
	<groupId>org.testng</groupId>
	<artifactId>testng</artifactId>
	<version>5.8</version>
	<scope>test</scope>
	<classifier>jdk15</classifier>
</dependency>

Você também tem que adicionar a dependência da api cliente do Selenium:

<dependency>
	<groupId>org.seleniumhq.selenium.client-drivers</groupId>
	<artifactId>selenium-java-client-driver</artifactId>
	<version>1.0-beta-2</version>
</dependency>

Outra configuração que você precisa fazer, é dizer ao TestNG que seus testes devem rodar em paralelo. Para isso você tem que configurar o maven-surefire-plugin.
Vai ficar assim:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-surefire-plugin</artifactId>
	<configuration>
		<parallel>tests</parallel>
		<threadCount>2</threadCount>
	</configuration>
</plugin>

Você tem dois possíveis valores para a configuração “parallel”: tests ou methods.

A threadCount equivale a quantos testes vão rodar ao mesmo tempo. No nosso caso vamos usar 2 pois só subimos duas instâncias de servidores Remote Control.

Após você já ter essa estrutura do Maven criada, vamos criar os testes em Java.

Para facilitar o exemplo, criei uma tela para podermos testar tudo isso. Essa tela é uma página de busca, com um input text para você digitar um termo e o botão buscar. Ao clicar no botão o resultado da busca aparece em uma tabela. Se não há resultado aparece uma mensagem informando que não encontrou.

Vamos criar então dois testes para essa tela:

  • Busca com resultado
  • Busca sem resultado

Abra então a Ide do Selenium no Firefox e grave o primeiro teste. Para isso digite o termo “Selenium” e mande buscar. Faça também um verificação de sucesso. Em seguida exporte-o para Java e salve no diretório testes-em-paralelo/src/test/java/br/com/seuenium/ com o nome TesteBuscaComResultado.java.

Faça o mesmo para o segundo teste, porém usando o termo “Seuenium” e salvando o arquivo como TesteBuscaSemResultado.java.

Temos agora duas classes Java que são dois TestCases. Quando se exporta para Java, a IDE cria uma classe de teste que extende um TestCase padrão do Selenium, a SeleneseTestCase. No nosso caso, vamos alterar isso. Vamos criar o nosso TestCase padrão, uma classe abstrata, onde as outras classes de teste irão extender esse cara.
O TestCase abstrato fica mais ou menos assim:

package br.com.seuenium;
 
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import com.thoughtworks.selenium.DefaultSelenium;
 
public abstract class AbstractTestCase {
 
	protected DefaultSelenium selenium;
 
	@BeforeTest(alwaysRun=true)
	public void startSelenium() {
		selenium = new DefaultSelenium("localhost", 4444, 
				"*chrome", "http://seuenium.com.br");
		selenium.start();
	}
 
	@AfterTest(alwaysRun=true)
	public void stopSelenium() {
		selenium.stop();
	}
}

Temos o método startSelenium() que inicializa uma sessão com o Selenium, que no nosso caso é o hub do grid. Temos também o comando stopSelenium() que fecha a sessão com o servidor do Selenium.
Colocando as anotações do TestNG @BeforeTest e @AfterTest, sempre que um teste for executado, ele primeiro vai executar o startSelenium(), em seguida vai rodar o teste e ao fim vai chamar o stopSelenium(). O parâmetro alwaysRun=true garante que mesmo que uma exceção ocorra, esses métodos serão executados.

Agora vamos para os nossos TestCases exportados pela IDE. Primeiro alteramos ele para extender a classe abstrata que acabamos de criar. Em seguida, vamos colocar a anotação @Test na própria classe, dando um valor para o parâmetro chamado “testName”. Você também precisa colocar a anotação @Test no método que contém as chamadas na api do Selenium.
O classe TesteBuscaComResultado então vai ficar assim:

package br.com.seuenium;
 
import static org.testng.Assert.*;
 
import org.testng.annotations.Test;
 
@Test(testName="Teste com resultado")
public class TesteBuscaComResultado extends AbstractTestCase {
 
	@Test
	public void testNew() throws Exception {
		selenium.open("/testes/4/buscar.php");
		selenium.type("q", "Selenium");
		selenium.click("//input[@value='Buscar']");
		selenium.waitForPageToLoad("30000");
		assertTrue(selenium.isTextPresent("2 resultados encontrados com a palavra Selenium"));
	}
}

Faça o mesmo com a outra classe TesteBuscaSemResultado .

Mas por que eu tenho que colocar a anotação @Test na classe e ainda por cima configurar o parâmetro testName?
Bom, isso eu demorei pra entender. Eu estava mais familiarizado com o Junit, onde um teste corresponde a um método anotado com @Test. Para o TestNG um teste corresponde a uma ou mais classes e o que define esse teste é o seu nome. Você até pode ter mais de um teste por classe, mas eles nunca rodarão em paralelo entre si, ou seja, todos os métodos com @Test da mesma classe rodarão sequencialmente.

Agora vamos rodar os testes em paralelo.
Na linha de comando, dentro do projeto testes-em-parelelo, digite:

mvn clean install

Com esse comando, o maven vai compilar as suas classes e vai chamar todos os seus testes.
O resultado esperado disso tem que ser um “BUILD SUCCESSFUL”.
Mas fique atento a parte com os prints dos seus testes. Você vai ver algo parecido com:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running TestSuite
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 10.557 sec
 
Results :
 
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

Note que 2 testes foram executados e demoraram 10.557 segundos.

Só para ter garantia de que você teve um ganho no tempo de execução dos seus testes, altere no pom.xml o threadCount para 1, para que seu teste rode sequencialmente. Você vai ter um resultado parecido como esse:

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 19.402 sec

Praticamente o dobro do tempo demorou.

Bom, vimos que é possível paralelizar os testes e ganhar tempo na execução. Há também um outro carinha que paraleliza testes em Java, é o Parallel Junit, mas eu não encontrei uma integração desse cara com o maven.
Mas e se meus testes não estão escritos em Java? Há frameworks que fazem isso para outras linguagens. Em Ruby por exemplo você tem o DeepTest. Para mais informações dê uma olhada no faq do Selenium Grid, clique aqui.

O projeto Maven completo com todas as classes e tudo configurado, pronto pra rodar, você pode baixar AQUI.

Bom, é isso. Qualquer dúvida só perguntar. :)