Testes e mais Testes! Quando parar?

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

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).

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?)

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?

[quote=thingol][quote]
Exceções! Sabemos que as checadas são para os erros recuperáveis e as não checadas são para os irrecuperáveis.
[/quote]

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).
[/quote]

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

Um IllegalArgumentException seria uma exemplo disso?

[quote=thingol][quote]
Exceções! Sabemos que as checadas são para os erros recuperáveis e as não checadas são para os irrecuperáveis.
[/quote]

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).
[/quote]

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:

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

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!

[quote=pcalcado][quote=thingol][quote]
Exceções! Sabemos que as checadas são para os erros recuperáveis e as não checadas são para os irrecuperáveis.
[/quote]

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).
[/quote]

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

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.

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ó.

OK!

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

Por que?

[]s valews

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.

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?

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

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’.

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!

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 {
   //...
 }
 //...

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

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).