Testes e mais Testes! Quando parar?

18 respostas
brunohansen

Salve pessoal do GUJ!

Estou aqui tentando testar o suficiente!

Não vou mentir! Tem sido muito chato testar, alguns testes são muito repetitivos. E isso para mim é um problema enorme, com certeza estou fazendo algo de muito errado!

A repetição dos testes tem ocorrido pelo seguinte fato: Métodos públicos que chamam outros métodos públicos.

Hoje eu repito os testes com base na seguinte justificativa: Hoje eu tenhio um métodos X que chama o método Y. Os testes no método X cobre inteiramente o método Y. Se eu testo unicamente o método X e amanhã eu faço com que o método X não chame mais o método Y eu perco a cobertura no método Y! Isso também acontece em construtores que chamam outros construtores! (Um código meu onde acontece isso vou colocar no final do post)

A questão é como lidar com essa situação? Duplico prevendo a cobertura do amanhã e perco tempo hoje duplicando? Não duplico hoje e se amanhã tiver necessida eu duplico. O pior é se eu esquecer de duplicar no amanhã!

Estou tentado a não duplicar! Acho que isso segue até aquela máxima do XP “Projetar hoje para os problemas de hoje e projetar amanhã para osproblemas de amanhã”, estou quase mudando para: “Pense no hoje, hoje e deixe para pensar no amanhã, amanhã!”

Fugindo do assunto um pouco!

Exceções! Sabemos que as checadas são para os erros recuperáveis e as não checadas são para os irrecuperáveis. Mas ai vai uma dúvida por que IllegalArgumentException é não checada? Você passar um argumento ilegal para um método é irrecuperável?

De qualquer forma tenho evitado ao máximo usar execeções checadas. É muito cansativo lidar com elas. Manter a assinatura de métodos conforme exceções vão sendo adicionadas ao projeto com o evoluir do tempo e chato pacas! O que vocês acham?

Este código tem o problema dos testes! Construtores chamando outros construtores e um construtor chamando métodos sets que são públicos!

/**
 * Esta classe representa um questionario.
 *
 * @since 1.0, 09/08/2007
 * @author Bruno Hansen e Conrado Ragazzi
 */
public class Questionario implements ResultavelIF{

	/**
	 * Titulo do questionario
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 */
	private String titulo;
	
	/**
	 * Informa se o questionario esta liberado para ser respondido
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 */
	private boolean liberado;
	
	/**
	 * Informa se o questionario possui resultados
	 *
	 * @since 1.0, 09/08/2007
	 * @author Bruno Hansen
	 */
	private boolean possuiResultado;
		
	/**
	 * Lista de questoes do questionario
	 *
	 * @since 1.0, 10/08/2007
	 * @author Bruno Hansen
	 */
	private List<Questao> questoes;
		
	/**
	 * Construtor que permite informar se o questionario esta 
	 * liberado ou nao para ser respondido.
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @param _titulo Titulo do questionario
	 * @param _liberado Informa se o questionario esta liberado para ser respondido
	 * @param _questoes Lista de questoes do questionario
	 * @throws ObjetoNullException
	 * @throws TamanhoMinimoException
	 * @throws AssociacaoException
	 */
	public Questionario(String _titulo, boolean _liberado, Collection<Questao> _questoes) {
		super();
		setTitulo(_titulo);
		setLiberado(_liberado);
		setQuestoes(_questoes);
	}
	
	/**
	 * Construtor que permite informar se o questionario esta 
	 * liberado ou nao para ser respondido.
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @param _titulo Titulo do questionario
	 * @param _liberado Informa se o questionario esta liberado para ser respondido
	 * @param _questao Questao do questionario
	 * @throws ObjetoNullException
	 * @throws AssociacaoException
	 */
	public Questionario(String _titulo, boolean _liberado, Questao _questao) {
		this(_titulo, _liberado, criaColecaoQuestoes(_questao));
	}
	
	/**
	 * Construtor que nao libera o questionario para ser respondido.
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @param _titulo Titulo do questionario
	 * @param _questoes Lista de questoes do questionario
	 * @throws ObjetoNullException
	 * @throws TamanhoMinimoException
	 * @throws AssociacaoException
	 */
	public Questionario(String _titulo, Collection<Questao> _questoes) {
		this(_titulo, false, _questoes);
	}
	
	/**
	 * Construtor que nao libera o questionario para ser respondido.
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @param _titulo Titulo do questionario
	 * @param _questao Questao do questionario
	 * @throws ObjetoNullException
	 * @throws AssociacaoException
	 */
	public Questionario(String _titulo, Questao _questao) {
		this(_titulo, false, _questao);
	}
	
	/**
	 * Pega o valor do titulo.
	 * 
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @return Valor do titulo.
	 */
	public String getTitulo() {
		return titulo;
	}

	/**
	 * Seta o valor do titulo.
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @param _titulo Valor para ser setado.
	 * @throws ObjetoNullException
	 * @throws TamanhoMinimoException
	 */
	public void setTitulo(String _titulo) {
		ObjetoVerificador.verificaNull(_titulo, "Questionario.titulo");
		StringVerificador.verificaTamanhoMinimo(_titulo, "Questionario.titulo", 5);
		titulo = _titulo;
	}
	
	/**
	 * Pega o valor do liberado.
	 * 
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @return Valor do liberado.
	 */
	public boolean isLiberado() {
		return liberado;
	}

	/**
	 * Seta o valor do liberado.
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @param _liberado Valor para ser setado.
	 */
	public void setLiberado(boolean _liberado) {
		liberado = _liberado;
	}
	
	/**
	 * Seta o valor do possuiResultado.
	 *
	 * @since 1.0, 09/08/2007
	 * @author Bruno Hansen
	 * @param _possuiResultado Valor para ser setado.
	 */
	public void setPossuiResultado(boolean _possuiResultado) {
		this.possuiResultado = _possuiResultado;
	}
	
	public boolean possuiResultado() {
		return possuiResultado;
	}
	
	/**
	 * Pega as questoes.
	 * 
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @return Valor do questoes.
	 */
	public OrdenavelIF<Questao> getQuestoes() {
		OrdenadorIF<Questao> ordenadorL;
		OrdenavelIF<Questao> ordenavelL;
		
		ordenadorL = new ListOrdenadorResultavel<Questao>(questoes, this);
		ordenavelL = new Ordenavel<Questao>(ordenadorL);
		
		return ordenavelL;
	}
		
	/**
	 * Pega as questoes. 
	 * (Deve ser utilizado somente pela classe Questao para gerenciar a 
	 * associacao bidirecional entre as classes Questao e Questionario.)
	 * 
	 * @since 1.0, 05/05/2007
	 * @author Conrado Ragazzi e Bruno Hansen
	 * @return Questoes.
	 */
	public Collection<Questao> getColecaoQuestoes() {
		return questoes;
	}
		
	/**
	 * Adciona uma questao ao questionario
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen e Conrado Ragazzi
	 * @param _questao A questao a ser adicionada
	 * @throws JaPossuiResultadoException
	 * @throws TamanhoMinimoException
	 * @throws ObjetoNullException 
	 */
	public void adicionarQuestao(Questao _questao){
		ObjetoVerificador.verificaNull(_questao, "Questionario.questao");
		_questao.setQuestionario(this);
	}
	
	/**
	 * Remove uma questao do questionario
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen e Conrado Ragazzi
	 * @param _questao A questao a ser removida
	 * @throws JaPossuiResultadoException
	 * @throws TamanhoMinimoException
	 * @throws ObjetoNullException 
	 * @throws NaoContemObjetoException
	 */
	public void removerQuestao(Questao _questao){
		ObjetoVerificador.verificaNull(_questao, "Questionario.questao");
		ColecaoVerificador.verificaContencao(getColecaoQuestoes(), "Questionario.questoes", _questao);
		_questao.setQuestionario(null);
	}
	
	/**
	 * Seta o valor do questoes.
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @param _questoes Valor para ser setado.
	 * @throws ObjetoNullException
	 * @throws TamanhoMinimoException
	 * @throws AssociacaoException
	 */
	private void setQuestoes(Collection<Questao> _questoes) {
		ObjetoVerificador.verificaNull(_questoes, "Questionario.questoes");
		QuestaoVerificador.verificaAssociacaoQuestionario(_questoes, "Questionario.questoes");
		ColecaoVerificador.verificaTamanhoMinimo(_questoes, "Questionario.questoes", 1);
		
		questoes = new ArrayList<Questao>();
		for(Questao questaoL : _questoes)
			adicionarQuestao(questaoL);		
	}
	
	/**
	 * Metodo utilitario que cria uma colecao contendo 
	 * a questao passada como parametro 
	 *
	 * @since 1.0, 18/08/2007
	 * @author Bruno Hansen
	 * @param _questao Questao a ser adicionada na colecao
	 */
	private static Collection<Questao> criaColecaoQuestoes(Questao _questao) {
		List<Questao> listaL;
		
		listaL = new ArrayList<Questao>();
		listaL.add(_questao);
		
		return listaL;
	}
	
}

Obrigado pela contribuição de todos!

[]s

18 Respostas

T

Não, não, não - as checadas são para as exceções externas (por exemplo, entrada incorreta de dados é ParseException), e as não checadas para as exceções internas (exemplo: um erro de programação que provoca um ArrayOutOfBoundsException).

cv1

Bruno, mostra pra gente um dos seus testes!

(E voce esta escrevendo essa tonelada e meia de javadoc pra que? Quem vai ler/manter isso atualizado?)

A

cv, você acredita que para uma soft-house um fonte com essa quantidade de javadoc é inutil ou tem q ser moderada?

Javadoc desse jeito somente para API, frameworks e etc?

pcalcado

thingol:

Exceções! Sabemos que as checadas são para os erros recuperáveis e as não checadas são para os irrecuperáveis.

Não, não, não - as checadas são para as exceções externas (por exemplo, entrada incorreta de dados é ParseException), e as não checadas para as exceções internas (exemplo: um erro de programação que provoca um ArrayOutOfBoundsException).

Não exatamente. Exceções não checadas cobrem situações que não ocorrem em situações normais, irrecuperáveis ou não.

ramilani12

Um IllegalArgumentException seria uma exemplo disso?

brunohansen

thingol:

Exceções! Sabemos que as checadas são para os erros recuperáveis e as não checadas são para os irrecuperáveis.

Não, não, não - as checadas são para as exceções externas (por exemplo, entrada incorreta de dados é ParseException), e as não checadas para as exceções internas (exemplo: um erro de programação que provoca um ArrayOutOfBoundsException).

Poxa fico realmente confuso!

Dando uma olhada aqui (http://blog.caelum.com.br/2006/10/07/lidando-com-exceptions/) vemos:

Nesse resumo aqui (http://br.geocities.com/vanessasabino/java/ej6.html) vemos:

brunohansen

cv:

(E voce esta escrevendo essa tonelada e meia de javadoc pra que? Quem vai ler/manter isso atualizado?)

Hehehe! Ainda bem que alguém perguntou.

Esta classe faz parte do meu humilde TCC. E como parte da documentação do TCC é solicitado que se entregue um dicionário de objetos, devido a este foi negociado a entrega do java doc para fazer o papel de dicionário de objetos.

No fim esta sendo no mínimo interessante fazer algo tão organizadinho assim! Porém, acho que não é viável fazer isto profissionalmente.

Só não vou mostrar da classe que eu postei acima, pois não duplique os testes dela! Basicamente enjoei desta duplicação!

Vou anexar a classe de domínio e a classe de teste para não poluir mais ainda o post!

brunohansen

pcalcado:
thingol:

Exceções! Sabemos que as checadas são para os erros recuperáveis e as não checadas são para os irrecuperáveis.

Não, não, não - as checadas são para as exceções externas (por exemplo, entrada incorreta de dados é ParseException), e as não checadas para as exceções internas (exemplo: um erro de programação que provoca um ArrayOutOfBoundsException).

Não exatamente. Exceções não checadas cobrem situações que não ocorrem em situações normais, irrecuperáveis ou não.

IllegalArgumentException é uma exceção não checada! No meu caso, eu não poderia usa-la quando alguém informar um título com menos de 5 caracteres?

Se sim, ao meu ver, eu a estaria utilizando em uma condição normal. Em outras palavras, estaria utilizando uma exceção não checada em uma condição normal que é informar um título inválido.

bzanchet

Um teste deveria testar só um método… faça mocks nas chamadas para outros métodos, fora do escopo do teste!

}catch (Exception e) { fail("Não deveria ter gerado execeção"); } Pode apagar isso acima. Sem dó.

brunohansen

OK!

Como faço isso quando os “outros” métodos estão na mesma classe que esta sendo testada?

Por que?

[]s valews

pcalcado

Sim, pode.

Não, não é uma situação normal (tanto que é uma exceção :wink: ). Se seu contrato diz que você não pode receber strings com menos de 5 caracteres então se alguém te enviar uma é uma quebra de contrato, situação anormal e digna de exceção não-checada.

brunohansen

rs…

Enfim dei um chute certo!

Sua frase acima esta tendendo a me levar a pensar da seguinte forma:

  • Se uma exceção ocorre devido a uma quebra de contrato é não checada
  • Se uma exceção ocorre sem que um contrato seja quebrado e há possibilidade de recuperação é checada
  • Se uma exceção ocorre sem que um contrato seja quebrado e não há possibilidade de recuperação é não checada

O que vc acha?

pcalcado

Na verdade uma exceção checada faz parte do cotnrato da operação.

peczenyj

Sobre os testes, vc pode usar junto do JUnit o EMMA para ver a cobertura dos testes. Ele é facil de usar e vai gerar um report colorido das linhas que foram exercitadas (total ou parcialmente) pelo Junit. Tambem pode usar junto do seu servidor de aplicação e ver quanto do codigo foi verificado com os testes funcionais.

Se vc chegar a 60% do codigo coberto pelos testes, eu diria que parece um bom começo. É claro que isso significa que 40% das linhas de código nem chegam a ser exercitadas e a possibilidade de bugs existe.

Quando parar? Bom… isso é um grande depende. Teste só pode encontrar bugs, não garante que o programa funciona, ou melhor, não garante que funciona da forma como o cliente gostaria. Logo a pergunta não é ‘quando parar’, e sim ‘de que forma testar’.

cv1

Mais uma: olha pra esse nome, e me diz (sem consultar em lugar nenhum, claro) o que o codigo do teste faz: testQuestaoStringCollectionOfRespostaBooleanBoolean.

Leia em voz alta:

QUESTAO TEST:

  • TEST QUESTAO STRING COLLECTION OF RESPOSTA BOOLEAN BOOLEAN.

Vao achar que vc ta tendo uma overdose de cafeina e te mandar pra casa dormir. Uma dica eh comecar com a palavra “deveria”, pra ler:

Teste Questao:

  • Teste: deveria criar questao multipla escolha em branco
  • Teste: deveria criar questao de escolha simples em branco

Em codigo:

public class QuestaoTest { // ou TesteQuestao, se vc quiser tudo em pt public void testeDeveriaCriarQuestaoMultiplaEscolhaEmBranco() throws Exception { //... } //...

Da uma olhada no AgileDox!

Alexandre

Um approach que tenho utilizado na nomenclatura dos meus métodos de teste é a utilização de “_” entre as palavras, tornando a leitura mais fácil. Vi em um paper do Neal Ford, tipo:

public class QuestaoTest {
 public void teste_deveria_criar_questao_multipla_escolha_em_branco() throws Exception {
   //...
 }
 //...

melhor ainda se você usa java 1.5, com JUnit 4.4, pode retirar a palavra “teste”, caindo para o approach de Behaviour-Driven Development, onde vc deve identificar comportamentos para os objetos que vocês está verificando.

public class QuestaoTest {
 public void deveria_criar_questao_multipla_escolha_em_branco() throws Exception {
   //...
 }
 //...
A

AHAHAHAHAHAH :?

então eu estou fazendo tudo errado.

to colocando os testes com nomes: test1, test2, test3 … testN.

Aproveitando o topico.

Voces costumam testar os metodos privados da classe ?

Eu não testo ja que se estes falharem os metodos publicos já vão falhar.

Grato

peczenyj

Cara, eu já testei métodos privados, modificando-os para publicos (usando sed ‘s/private/public/g’ ) apenas para rodar c/ o Junit beeem no começo.

Entretanto se vc organizar bem os testes dos métodos públicos, vai exercitar os métodos privados de forma satisfatória (e deixa de fazer coisas estranhas).

Criado 20 de agosto de 2007
Ultima resposta 29 de ago. de 2007
Respostas 18
Participantes 10