Testes de unidade são uma realidade cada vez mais próxima das nossas fábricas de software, porem, é uma das coisas que tanto os profissionais de teste quando os profissionais de desenvolvimento desconhecem em sua maioria. Melhor dizendo, conhecer todos conhecem, mas ver funcionando, saber fazer e principalmente acreditar no benefício, não.
É um pouco difícil achar exemplos em blogs, sites ou comunidades de desenvolvimento e/ou testes, principalmente em português, mas um ótimo exemplo da aplicação de testes de unidade usando a ferramenta Visual Studio, para a plataforma .Net, está no excelente post “VSTS – Visual Studio Team System para Testadores – Unit Test” do Gustavo Quezada. No post ele descreve muito bem um resumo sobre o momento dos testes de unidade e também como aplicá-los tecnicamente.
Vou tentar elaborar aqui uma visão baseada em TDD (Test-driven development) usando a solução mais usada para Java, o Framework de testes de unidade JUnit, Desenvolvido por Kent Beck (XP) e Erich Gamma (GoF).
Primeiramente vamos pensar em como deve ser o micro processo para a implementação de TDD em alto nível:
1 – Escrever os Testes
2 – Executar os testes e verificar as falhas
3 – Escrever o código
4 – Rodar os testes para identificar sucesso
5 – Refatorar o código para corrigir defeitos e efetuar melhorias
Agora vamos ver isso funcionando:
1 – Escrever os Testes:
É premissa para a escrita dos testes termos todos os requisitos especificados e detalhados de forma a podemos avaliar os dados de entrada e os resultados esperados. Se você é um analista de teste já deve ter notado uma semelhança. A composição básica do nosso teste de unidade é a mesma dos nossos casos de teste, porem, ao escrever testesde unidade não nos preocupamos com as pré condições, procedimentos de teste e outros detalhes. Nosso objetivo aqui é claro: Fornecer o que a classe que será escrita precisará e receber o que ela nos fornecerá.
Vamos supor que a lista de requisitos da nossa classe é a seguinte:
“O programa deve ler 3 números inteiros. Os três valores serão interpretados como os comprimentos dos lados de um triângulo. O programa imprime uma mensagem sobre o tipo do triângulo”
Vamos pensar em algumas opções de respostas que podemos ter:
a – Se for isósceles
b – Se for equilátero
c – Se for Escaleno
d – Se a soma de dois lados for igual a do terceiro
e – Se a soma de dois lados for menor a do terceiro
Podemos listar também algumas opções de exceções:
a – Se algum lado for negativo ;
b – Se todos os comprimentos do triânngulo forem zero;
c – Se algum comprimento do triângulo for zero;
Enfim, para isso podemos criar inúmeros casos de teste, mas para esse exercício vamos escrever apenas 14 (quatorze) considerando que o usuário sempre vá entrar com valores numéricos.
Abaixo os “casos de teste” ainda em formato texto:
1 – Triângulo Escaleno
2 – Triângulo Equilátero
3 – Triângulo Isósceles
4 – Triângulo Isósceles
5 – Triângulo Isósceles
6 – Triângulo com Lado Nulo
7 – Triângulo com Lado Negativo
8 – Triângulo cuja soma dos lados A e B é igual a C
9 – Triângulo cuja soma dos lados A e C igual a B
10 – Triângulo cuja soma dos lados C e B igual a A
11 – Triângulo cuja soma dos dois lados menor que a terceiro”}
12 – Triângulo onde todos os lados são Nulos
13 – Triângulo cuja soma dos lados A e B é menor que C
14 – Triângulo cuja soma dos lados B e C é menor que B
Abaixo vamos escrever os dados de entrada o que esperamos com eles usando a seguinte sintax
(entradaA, entradaB, entradaC, resultadoEsperado), sendo que as entradas devem ser inteiros e o resultado uma String
1 – {2, 9, 10,”Escaleno”}
2 – {20, 20, 20, “Equilátero”}
3 – {20, 20, 30, “Isósceles”}
4 – {20, 30, 20, “Isósceles”}
5 – {30, 20, 20, “Isósceles”},
6 – {0, 2, 9, “Lado Nulo”}
7 – {3, -2, 9, “Lado Negativo”}
8 – {5, 6, 11, “Soma dos dois lados igual a terceiro”}
9 – {5, 11, 6, “Soma dos dois lados igual a terceiro”}
10 – {11, 6, 5, “Soma dos dois lados igual a terceiro”}
11 – {5, 6, 12, “Soma dos dois lados menor que a terceiro”}
12 – {0, 0, 0, “Todos os lados Nulos”}
13 – {5, 12, 6, “Soma dos dois lados menor que a terceiro”}
14 – {12, 5, 6, “Soma dos dois lados menor que a terceiro”}
É muito importante entender que o teste de unidade tem menos valor se aplicado após a classe estar escrita, principalmente porque a sua principal finalidade, economizar tempo na implementação da classe deixa de ser aproveitada, ficando somente os testes “de unidade de regressão” para modificações da classe. Portanto, é fundamental escrever os casos de teste antes mesmo que a classe que será testada, para essa atividade, é muito recomendável que o analista de teste e programador trabalhem juntos, pensando em caminhos e entradas que poderão ser usados na futura implementação.
Para escrever o teste vamos precisar do Eclipse e do JUnit.
O JUnit pode ser baixado no link: http://sourceforge.net/projects/junit/files/junit/ e incluído nas libraries do Java Build Path do projeto do eclipse.
Agora vamos entender o básico dos métodos (notation ) do JUnit:
@RunWith: Quando uma classe tem a notation @RunWith ou estende uma classe com o predecessor com @RunWith, JUnit irá chamar a classe que faz referência para executar os testes em que a classe em vez de o corredor construído em JUnit. Podemos implementar uma Suite de Testes com o parâmetro Suite.class ou uma lista de parâmetros com o parâmetro Parameterized.class (que será usada no nosso exemplo).
@Parameters: A notation de um método que cria uma coleção, array, lista ou outra estrutura de dados, de forma a garantir que não precisemos de vários métodos para executar uma sequencia ordenada de testes, através de uma sequencia de parâmetros que serão enviados, um a um, para o construtor da classe ao instânciar um objeto (teste).
@Test: A notation do método que realiza o teste. Normalmente esse método instância o objeto da classe que será testado e realiza comparações.
org.junit.Assert.*: Os métodos de comparação. realizam várias comparações como mostrado abaixo:
assertTrue : Verifica se o valor de retorno é true
assertFalse : Verifica se o valor de retorno é false
assertEquals : Compara dois valores de retorno
assertNotNull : Verifica se o valor de retorno não é null
assertNull : Verifica se o valor de retorno é null
assertSame : Confere se dois objetos referenciam o mesmo objeto
assertNotSame : Confere se dois objetos referenciam objetos diferentes
fail : usado para criar falha no teste via programação do teste
Para a lista completa dos métodos assert: http://www.junit.org/apidocs/org/junit/Assert.html
API completa do JUnit: http://www.junit.org/apidocs/
Agora vamos ver a classe de teste desenvolvida com base nos nossos dados de entrada e devidamente comentada para facilitar o entendimento:
// Importamos as classes que precisamos para usar os métodos citados
// Importante importar essa como static, pois usamos os métodos estáticos para realizar as comparações.
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/* Usar o RunWith informando que vamos usar uma classe parametrizada.
* Isso fará com que ao ser instanciada como JUnit Test, a classe crie um objeto usando os parâmetros informados
* no método Parameters para realizar cada teste, economizando dezenas de linhas de código.
*/
@RunWith(Parameterized.class)
//Declaramos uma classe normal
public class TesteExemplo {
// Declaramos os inteiros que representarão os lados do triângulo
private int a;
private int b;
private int c;
// Declaramos a string que representará o resultado esperado
private String tipo;
// Criamos o construtor que receberá os parâmetros para execução do teste
public TesteExemplo(int a, int b, int c, String trianguloEsperado) {
super();
// Cada lado do triângulo recebe um valor que vem dos parâmetros da classe
this.a = a;
this.b = b;
this.c = c;
// O resultado esperado recebe o ultimo parâmetro
this.tipo = trianguloEsperado;
}
// Método que retorna uma coleção com os parâmetros que serão usados no construtor instânciado para os testes
@Parameters
public static Collection carregaTriangulosDeTeste(){
return Arrays.asList(
new Object [][]{
// Como array em Java começa no 0, vamos incluir o teste 1 na posição 0 do array e assim por diante
//Test0
{2, 9, 10,"Escaleno"},
//Test1
{20, 20, 20, "Equilátero"},
//Test2
{20, 20, 30, "Isósceles"},
//Test3
{20, 30, 20, "Isósceles"},
//Test4
{30, 20, 20, "Isósceles"},
//Test5
{0, 2, 9, "Lado Nulo"},
//Test6
{3, -2, 9, "Lado Negatívo"},
//Test7
{5, 6, 11, "Soma dos dois lados igual a terceiro"},
//Test8
{5, 11, 6, "Soma dos dois lados igual a terceiro"},
//Test9
{11, 6, 5, "Soma dos dois lados igual a terceiro"},
//Test10
{5, 6, 12, "Soma dos dois lados menor que a terceiro"},
//Test11
{0, 0, 0, "Todos os lados Nulos"},
//Test12
{5, 12, 6, "Soma dos dois lados menor que a terceiro"},
//Test13
{12, 5, 6, "Soma dos dois lados menor que a terceiro"},
}
);
}
// Método que executa o teste a cada instanciação do objeto da classe teste
@Test
public void validaTriangulo() {
// Vamos criar um objeto do tipo Trianglulo (nossa futura classe que ainda vai existir) e passar os parâmetros do seu construtor
Triangulo escalenoValido = new Triangulo(a, b, c);
// Realizamos a comparação entre o valor que foi retornado e o valor que é esperado
assertEquals(escalenoValido.retornarTipo(), tipo);
}
}
2 – Executar os testes e verificar as falhas
Agora vamos executar a primeira vez e verificar o que o JUnit nos retorna:

É importante que todos os testes falhem, para termos certeza que estão corretos (irônico não?). Isso porque os resultados esperados não podem existir, já que nem a classe do objeto que vamos testar existe.
3 – Escrever o código
Agora vou escrever uma classe com alguns erros de lógica e outros no conteúdo da resposta:
public class Triangulo {
private int a, b, c;
public Triangulo(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
public String retornarTipo() {
// erro de lógica
if((this.a == 0) || (this.b == 0) || (this.c == 0))
return "Todos os lados Nulos";
if((this.a == 0) || (this.b == 0) || (this.c == 0))
// Erro no retorno
return "Lado Núlo";
if((this.a < 0) || (this.b < 0) || (this.c < 0))
return "Lado Negatívo";
if((this.a == this.b) &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; (this.b == this.c))
return "Equilátero";
if(
((this.a != this.b) &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; (this.b == this.c))||
((this.a == this.b) &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; (this.b != this.c))||
((this.a == this.c) &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; (this.b != this.c))
)
// Erro no retorno
return "Isóceles";
if( (this.a + this.b == this.c)||
(this.b + this.c == this.a)||
(this.c + this.a == this.b)
)
return "Soma dos dois lados igual a terceiro";
if( (this.a + this.b < this.c)||
(this.b + this.c < this.a)||
(this.c + this.a < this.b)
)
return "Soma dos dois lados menor que a terceiro";
if((this.a != this.b) &amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp; (this.a != this.c))
return "Escaleno";
return null;
}
}
4 – Rodar os testes para identificar sucesso
Agora vamos verificar a nova execução do testede unidade:

Na imágem acima podemos ver que o teste nos retorna três informações muito importantes:
1 – Quantos casos de teste de unidade passaram;
2 – Quais casos de teste de unidade passaram;
3 – Qual a diferença entre o resultado esperado e o resultado recebido
Essa ultima informação em especial é que dá o “caminho das pedras” para o desenvolvedor corrigir com maior facilidade.
5 – Refatorar o código para corrigir defeitos e efetuar melhorias
Efetuamos as melhorias:
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class TesteExemplo {
private int a;
private int b;
private int c;
private String tipo;
public TesteExemplo(int a, int b, int c, String trianguloEsperado) {
super();
this.a = a;
this.b = b;
this.c = c;
this.tipo = trianguloEsperado;
}
@Parameters
public static Collection carregaTriangulosDeTeste(){
return Arrays.asList(
new Object [][]{
//Test0
{2, 9, 10,"Escaleno"},
//Test1
{20, 20, 20, "Equilátero"},
//Test2
{20, 20, 30, "Isósceles"},
//Test3
{20, 30, 20, "Isósceles"},
//Test4
{30, 20, 20, "Isósceles"},
//Test5
{0, 2, 9, "Lado Nulo"},
//Test6
{3, -2, 9, "Lado Negatívo"},
//Test7
{5, 6, 11, "Soma dos dois lados igual a terceiro"},
//Test8
{5, 11, 6, "Soma dos dois lados igual a terceiro"},
//Test9
{11, 6, 5, "Soma dos dois lados igual a terceiro"},
//Test10
{5, 6, 12, "Soma dos dois lados menor que a terceiro"},
//Test11
{0, 0, 0, "Todos os lados Nulos"},
//Test12
{5, 12, 6, "Soma dos dois lados menor que a terceiro"},
//Test13
{12, 5, 6, "Soma dos dois lados menor que a terceiro"},
}
);
}
@Test
public void validaTriangulo() {
Triangulo escalenoValido = new Triangulo(a, b, c);
assertEquals(escalenoValido.retornarTipo(), tipo);
}
}
Agora efetuamos as melhorias necessárias e re-executamos os casos de teste até que todos passem:

Esse foi um exemplo de implementação de teste de unidade ou teste de unidade inspirados em TDD, prática ágil muito eficaz na identificação de defeitos. Nem todas as implementações são tão fáceis, ou gastam pouco tempo, mas, com o tempo e alguma prática, esse tipo de atividade pode se tornar menos custosa e mais eficiente.
Peço desculpas se algum conceito apresentado acima está divergente das melhores práticas ou de algum padrão ou conceito, e me prontifico a efetuar quaisquer correções. O exemplo citado aqui é ilustrativo.
TDD, testesde unidade, automação de testes funcionais e de performance, entre outras áreas das disciplinas de Teste de Software e Arquitetura de Software ainda são muito misteriosas e discutidas, mas pouco implementadas, principalmente aqui no Brasil. Porem, é muito importante que nossos analistas de teste busquem essa capacitação técnica para melhorar a nossa posição no mercado, melhorar a qualidade do software e da mão de obra brasileira e acabar com mitos como “o desenvolvedor é um profissional mais estudioso ou mais técnico do que o teste” ou “teste é uma atividade simples de clicar e executar alguns fluxos”.
Mantenho-me disponível para quaisquer esclarecimentos
Essa atividade é baseada em um exercício em sala no curso de Especialização em Ciência da Computação com Ênfase em Engenharia de Software da Universidade Federal de Minas Gerais (UFMG) .
Bons Testes
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.

Categories
Tag Cloud
Blog RSS
Comments RSS
Last 50 Posts
Back
Void « Default
Life
Earth
Wind
Water
Fire
Light 
Simplesmente SENSACIONAL!!! Gostei Muito!!!
Devagar estamos conseguindo mostrar como Unit Test é importante e simples de implementar!
Até+,
Quezada
Apoiado!
Realmente. Unit Test é outra “cabeça de bacalhau” da nossa área, assim como automação de testes funcionais, automação de testes de carga e estresse, modelo V, inspeções, análise de caixa branca e etc.
Todo mundo sabe (ou diz) que é importante, mas quase ninguém implementa. Ou por falta de conhecimento, ou por achar muito complicado ou mesmo por não acreditar nos benefícios e economia que essas técnicas agregam aos nossos projetos de desenvolvimento de software.
Por isso é tão importante que existiram mais publicações técnicas sobre esses assuntos, desmistificando-os, alinhando conceitos sobre as atividades de teste de software e retirando aquela visão errônea que o testador tem menos conhecimento do que o desenvolvedor.
Muito obrigado pelo comentário e atenção.
Abraços.
Ficou show o artigo!
Vê o exemplo clássico do triângulo (utilizado pelo Glenford Myers na primeira edição do “The Art of Software Testing”, isso em 1979!), foi como vê a versão resmaterizada de um filme.
Quando se fala em TDD, é importante lembrar que ele é sobre modelar/projetar a funcionalidade. Os testes (a princípio) são apenas um meio, não o fim.
Depois que a funcionalidade já estiver pronta, os testes que até então só foram feitos para nos ajudar na modelagem da funcionalidade, acabam sendo usados como testes de regressão e ajudam o desenvolvedor a ter ciência sobre a qualidade do código e se as mudanças realizadas não quebraram nada.
Ou seja, TDD nos ajuda antes (entendendo melhor a funcionalidade a ser desenvolvida), durante (implementando de forma ciente [nada de eu acho] e testável a funcionalidade) e depois do desenvolvimento (garantindo a qualidade da funcionalidade).
Parabéns Camilo!
Abraços!
Excelente comentário Fabrício.
O Teste de unidade é uma “técnica” para melhorar a codificação e evitar o retrabalho com correção de defeitos quando o sistema estiver com uma complexidade muito superior do que no momento da codificação inicial, tanto é, que não é normalmente atribuído ao analista de teste e sim ao desenvolvedor.
Fico feliz em saber que outras pessoas compartilham do hábito de ler livros que tem uma década a mais que a própria idade haha. Apesar de ser um exemplo de 79, o exemplo do triângulo é um dos melhores até os dias de hoje.
Abraços e obrigado novamente pelo excelente comentário!
[...] Uma Introdução a TDD com JUnit – Camilo Ribeiro (The Bug Bang Theory); [...]
[...] Uma Introdução a TDD com JUnit – Camilo Ribeiro (The Bug Bang Theory); [...]
Estou divulgando a campanha que lancei com o SINDPD (Sindicato de Processamento de Dados de SP).
Vamos repassar, pois este orgão jamais nos ajudou e está fazendo jogo político:
http://zarroboogsfound.blogspot.com/2010/03/sindpd-esta-defendendo-quem-fere-o.html
Obrigado!
Camilo:
Cheguei aqui a partir de um twit do Fabricio (QualidadeBR), e estou comentando no seu blog pela primeira vez.
Não é muito simpático estrear os comentários colocando uma crítica, principalmente quando se percebe que foi feito com seriedade e está muito bem escrito. Mas acho que a discussão de ideias é o objetivo principal de um blog.
A principal crítica é a seguinte: não foi abordada aqui a premissa dos passos de bebê (baby steps). O tamanho dos testes apresentados e do código de produção é muito grande. Deveria ser uma assertiva por ciclo, não uma classe por ciclo.
Gostei da figura, reflete bem a técnica, mas a escala de tempo/tamanho do código em TDD é bem menor. A metáfora que costumo usar é a do velocista deficiente visual: a escrita dos testes (o atleta guia que enxerga) vão na mesma velocidade que a escrita do código (o atleta que compete).
Outra questão: “ter todos os requisitos especificados e detalhados” não me parece um pré-requisito, mas uma sina. Quando vejo requisitos muito detalhados, fico com uma pulga atrás da orelha: geralmente são produto de especulação. Infelizmente, é essa a regra quando se trabalha no modelo de fábrica.
É escrevendo exemplos (também conhecidos como testes) que conseguimos esclarecer e detalhar o que se espera ser desenvolvido.
Espero ter contribuído,
Jorge Diz
twitter: @jorgediz
Olá Jorge Diz,
É um enorme prazer ter você comentando aqui no BugBang. Primeiramente, muito obrigado pelo comentário.
Muito obrigado também pelas críticas e pelos elogios. E sim, o principal intuito aqui é me fazer pesquisar, estudar e ajudar quem também está em busca de algum conhecimento, portanto, as críticas são muito bem vindas sempre
O objetivo principal do post era demonstrar de uma forma mais simples e prática como podemos desenvolver testes de unidade e como eles podem nos ajudar a evitar problemas em implementações relativamente simples, talvez como um acompanhamento do post comentado do Quezada no lado “Java da força”.
Como você sabe, os testes de unidade ainda são uma prática longe de ser reconhecida como amplamente abordada nas empresas de software (principalmente no Brasil), em função de vários mitos como o do trabalho dobrado ou o do gap entre os testes e os fontes (ótimamente desmistificado pela metáfora do seu comentário).
Nesse caso, não queria preencher inúmeras linhas falando sobre o que uma rápida pesquisa no Google poderia proporcionar, ou sobre o que vemos nas salas das nossas universidades, mas sim sobre uma forma simples, com um cenário conhecido e com um exemplo passo a passo, de forma a incentivar a prática dessa técnica.
Outra coisa importante. Eu não sou um profundo conhecedor do mundo ágil, embora esteja gastando boa parte do meu tempo a estudos nos últimos meses, e possivelmente, em futuros posts sobre o assunto poderemos ver um pouco do amadurecimento nessas práticas fantásticas e inovadoras, além é claro, de outros pontos a serem contrariados assim como os desse post.
Mais uma vez muito obrigado. Suas críticas já estão me orientando a buscar novos conhecimentos
Abraços.
Olá Camilo,
Muito interessante este artigo!
Lembrei da época da universidade, quando tive o primeiro contato com o TDD…
É incrível como não “damos bola” a técnicas relativamente simples, mas de grande valia como essa em questão.
Parabéns pelo artigo!
Esse é um tema para ser discutido mais vezes!
Alex Mestre!
Obrigado pelo comentário.
É verdade.
Teste de unidade é algo presente em todos os livros de teste, em todas as ementas de certificações, no vocabulário das universidades e em vários outras fontes de conhecimento relativas a teste de software, mas infelizmente não é parte do dia a dia da maioria dos desenvolvedores, gerentes, engenheiros de software das nossas empresas.
Tanto que pra muitos é um mito, um trabalho em dobro, um esforço sem retorno ou algo extremamente complexo, quando na verdade não é.
O objetivo aqui é desmistificar um pouco dessa atividade que pode economizar muito do nosso tempo e esforço. Em futuras publicações vou tentar falar mais sobre os aspectos gerenciais do TDD com alguns exemplos mais complexos usando o JUnit.
Abraços.