Nós, desenvolvedores, deveríamos estar acostumados com a criação de testes automatizados, pois escrevê-los faz parte do processo de metodologias ágeis.
Sem processos de desenvolvimento ágeis será que você é ágil mesmo?
Essa é um pergunta para refletirmos. O simples fato de utilizamos alguma metodologia como Scrum, Kamban, Lean, e continuarmos desenvolvendo da mesma maneira, acredito eu que seja um dos erros de equipes que se julgam ágeis.
Mas voltando ao nosso foco, que é conversar sobre testes automatizados, vamos falar um pouco mais sobre o framework DBUnit. Ele é uma extensão do JUnit para criação de testes de banco de dados, entre outras coisas. Escrever objetos Mocks para fazer testes em que há acesso a banco de dados é uma estratégia para resolver esse tipo de teste. Outra estratégia seria acessar uma base de dados de desenvolvimento e executar os testes nela, muitos frameworks fazem dessa forma.
Utilizando o DBUnit teremos algumas facilidades como:
- Limpeza de registros após testes;
- Comparação direta de registros;
- Retorno de estado pós-teste;
- Popular banco antes do teste.
O DBUnit irá funcionar como um facilitador para os nossos testes.
Criando nossos Teste com JUnit, DBUnit e Spring
Construiremos nossa aplicação baseado em JUnit 4.x, pois é muito mais confortável utilizar anotações de testes e o Spring para fazer a nossa injeção de dependência, assim podemos separar configurações de testes da de implementação real. Para fazer o nosso build e gerenciar as dependências, vamos utilizar do Maven 2 (Confira as novidades do Maven 3.0 aqui).
Adicionando as dependências no nosso pom.xml
Editaremos nosso pom.xml para que atenda às nossas necessidades, para isso vamos adicionar as dependências do JUnit 4.x, DBunit e ao Spring conforme abaixo:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>brenooliveira</groupId>
<artifactId>testes-automatizados</artifactId>
<packaging>war</packaging>
<version>1.0.0-SNAPSHOT</version>
<name>Testes Automatizados com JUnit, DBUnit e Spring</name>
<url>http://www.brenooliveira.com.br/</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.4.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerVersion>1.6</compilerVersion>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
No pom.xml acima apenas adicionamos os JARS do JUnit, DBUnit e dois JARS do spring um para testes.
O que iremos fazer nos testes
O exemplo será um simples update de tabela no MySQL, mas com o DBUnit você pode testar Querys mais elaboradas e inserções, entre outras coisas.
Configurando nosso DBUnit
Vamos preparar o ambiente para utilizar o DBUnit. Ele precisa de um arquivo XML, chamado dataset.xml, e na maior parte do tempo vamos trabalhar com XMLs, onde vamos definir onde nos colocamos as estruturas de nossas tabelas. No exemplo, teremos uma tabela Pessoas com a seguinte estrutura SQL:
CREATE TABLE pessoas(
id int not null auto_increment,
nome varchar(255) not null,
aniversario date not null,
primary key (id)
);
Agora vamos definir o arquivo dataset.xml conforme abaixo:
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<person id="1" nome="Breno Oliveira" aniversario="1985-12-26" />
</dataset>
Observe que o XML criado é muito parecido com o SQL. Os nomes dos campos do SQL se transformam propriedades da nossa tag.
Temos nosso primeiro dataset, mas o DBUnit ainda espera um dataset de retorno. Como mencionei, vamos fazer apenas um update na tabela. Então nosso dataset ficará da seguinte forma:
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<person id="1" nome="Breno Oliveira Alterado" aniversario="1985-12-26" />
</dataset>
Configurando nosso DataSource com o Spring
O Bean abaixo define o DataSource para a conexão com um banco de dados MySQL:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p">
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/spronline"
p:username="root" p:password="123456" />
</beans>
Sem muitas novidades na definição deste bean, ele apenas faz uma conexão com um banco de dados MySQL.
Escrevendo nosso teste
O passos para nossos teste serão:
- Carregar os dados antes para nossos métodos de teste;
- Vamos carregar o banco de dados por JDBC para simular a aplicação;
- Realizar os testes propriamente ditos, nos quais vamos comparar se o valor alterado está conforme o nosso XML de resultado esperado;
- E, finalizando nossa classe, iremos limpar a base de dados.
Observe a nossa classe de teste:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext.xml"})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class})
public class TestDBUnitWithSpring {
@Autowired
private DataSource dataSource;
@Before
public void init() throws Exception{
// Insere os dados no banco de dados
DatabaseOperation.CLEAN_INSERT.execute(getConnection(), getDataSet());
}
@After
public void after() throws Exception{
//Limpa a base de dados
DatabaseOperation.DELETE_ALL.execute(getConnection(), getDataSet());
}
private IDatabaseConnection getConnection() throws Exception{
// Pega a conexão com o banco de dados
Connection con = dataSource.getConnection();
DatabaseMetaData databaseMetaData = con.getMetaData();
IDatabaseConnection connection = new DatabaseConnection(con,databaseMetaData.getUserName().toUpperCase());
return connection;
}
private IDataSet getDataSet() throws Exception{
// Pega o arquivo de para inserir
File file = new File("src/test/resources/dataset.xml");
return new FlatXmlDataSet(file);
}
@Test
public void testSQLUpdate() throws Exception{
Connection con = dataSource.getConnection();
Statement stmt = con.createStatement();
// Pega o valor atual
ResultSet rst = stmt.executeQuery("select * from pessoas where id = 1");
if(rst.next()){
// compara a partir de dataset.xml
assertEquals("Breno Oliveira", rst.getString("nome"));
rst.close();
// atualiza via SQL
int count = stmt.executeUpdate("update pessoas set nome='Breno Oliveira Alterado' where id=1");
stmt.close();
con.close();
// expera somente 1 linha de alteração
assertEquals("one row should be updated", 1, count);
// Fetch database data after executing the code
QueryDataSet databaseSet = new QueryDataSet(getConnection());
// filtra os dados
databaseSet.addTable("pessoas", "select * from pessoas where id = 1");
ITable actualTable = databaseSet.getTables()[0];
// Carrega os dados esperados a partir do XML
IDataSet expectedDataSet = new FlatXmlDataSet(new File("src/test/resources/expectedDataSet.xml"));
ITable expectedTable = expectedDataSet.getTable("pessoas");
// Filtra colunas desnessarias dos dados atuais definidos pelo XML
actualTable = DefaultColumnFilter.includedColumnsTable(actualTable, expectedTable.getTableMetaData().getColumns());
// Assert da base de dados atual com os dados esperados
assertEquals(1,expectedTable.getRowCount());
assertEquals(expectedTable.getRowCount(), actualTable.getRowCount());
assertEquals(expectedTable.getValue(0, "nome"), actualTable.getValue(0, "nome"));
} else {
fail("no rows");
rst.close();
stmt.close();
con.close();
}
}
}
Anotamos a classe com @RunWith(SpringJUnit4ClassRunner.class) para o JUnit rodar os testes com o Spring Test. E também anotamos a classe com o @ContextConfiguration(locations={“classpath:applicationContext.xml”}), que define o nosso arquivos de XML onde estão nossas configurações, mas você poderia criar um arquivo somente para os testes. As demais observações deixei no código.
Para o Spring realizar a injeção de dependência, adicionamos @TestExecutionListeners({DependencyInjectionTestExecutionListener.class}), assim podemos usar o nosso @Autowired para nosso Bean de DataSource.
As demais partes do teste são detalhes. Como o foco aqui era exibir como fazer a integração, deixarei somente nos comentários do código.
Comentem!