segunda-feira, 17 de março de 2008

Devo mockar métodos de classes "utilitárias"?

Se considerarmos que esses métodos estão fora do escopo testado ou que serão testados isoladamente, a resposta é não, porém, pensando com cuidado, percebo que esses métodos normalmente são recheados de Reflections e Generics para serem o mais genérico possível, e pensando em algumas situações acho que podemos mudar de opinião.

Temos aqui na empresa uma classe CollectionUtil onde criamos métodos para manipular Collections. Nesta classe criamos um método não tão genérico quanto o possível, porém, o suficiente para a nossa utilização, sendo que me deparei com uma coisa:

O método é responsável por receber uma Collection e retornar um Array de ids (todas as nossas entidades tem esse atributo):

 0: public Long[] convertCollectionToArrayIds(
1: Collection coll)
2: {
3: Method mKey = null;
4: Long[] ids = new Long[coll.size()];
5:
6: try
7: {
8: int count = 0;
9: for(T clazz: coll)
10: {
11: mKey = clazz.getClass().
12: getMethod("getId");
13: ids[count] = (Long) mKey.invoke(
14: clazz,new Object[]{});
15: count++;
16: }
17:
18: return ids;
19: }
20: catch(Exception e)
21: {
22: e.printStackTrace();
23: return null;
24: }
25: }

Imagine uma situação onde alguém, não importa o porque, não use o atributo "id" e sim "cod" ou um erro de digitação e sai um "ide", ou qualquer outra coisa, pensando nisso, vejo que a melhor saída é: Não mockar o método "utilitário".

Use o bom senso, um método utilitário que envie um email ou que não seja tão genérico assim, pode ser mockado. Outra alternativa é enriquecer o método "utilitário", fazendo verificações se a entidade possui o atributo "id".

terça-feira, 11 de março de 2008

Mais sobre Cobertura de Código (Code Coverage)

Mais uma vez caí numa discussão sobre garantir ou não 100% de cobertura, na ultima vez que discuti o assunto caí num ponto onde 100% de Cobertura só serve para o marketing da minha empresa, ou setor ou de um produto, de qualquer forma, somente para Propaganda. Acontece que algumas coisas foram mudando minha opinião, e achei que deveria atualizar a opinião do Blog também.

* Sem garantir os 100% de Cobertura, fica complicado verificar se o que deve ser testado, foi testado. O responsável, ou toda a equipe tem que ficar constantemente verificando a cobertura de uma determinada classe ou de um determinado pacote;
* Alterações em um método que não deveria ser testado fazem com que ele precise de teses. Num "getzinho" simples que só retorna uma data, você pode incluir um regra que: se não houver data no objeto, retorne a data de hoje. Se o desenvolvedor esquecer de fazer o teste, dificilmente alguém vai perceber a ausência dele;
* Num momento de refactoring, a confiança nos testes fica abalada. Se você não sabe se todo o sistema esta coberto, como você vai garantir que suas alterações não quebraram o código? Resposta fácil, procurando a classe de testes responsável pelo método que esta sendo refatorado e verificando se existe algum teste para aquele método específico.

Quando pensamos em todos esses pontos, e percebemos outros como o que vi no Blog do Wagner Elias, ótimo texto por sinal, percebemos que garantir a cobertura de código é muito válido, não somente a nível de Propaganda.

Uma alternativa muito boa é automatizar alguns testes, ou gerar o código das situações mais genéricas, prefira sempre a primeira situação, manter código desnecessário é um custo muito alto. Sendo assim, um get ou set estaria sendo coberto por esse teste genérico, até ter sua implementação alterada, neste caso um teste "de verdade" seria criado para ele.

Para ver os outros post sobre o assunto.

quinta-feira, 6 de março de 2008

Método mockado retornando uma Exception

Recentemente precisei fazer um teste em um método e que deveria verificar o comportamento do mesmo quando um método invocado por ele retornasse uma Exception específica.

Exemplo do método:
0: public String delete() throws Exception
1: {
2: try
3: {
4: vendaManager.delete(venda.getId());
5: }
6: catch (UnexpectedRollbackException e)
7: {
8: addActionError("Não foi possível cancelar venda.");
9: return Action.INPUT;
10: }
11: return Action.SUCCESS;
12: }
No expect do método é só retornar a exception da seguinte forma:
0: vendaManager.expects(once()).method("delete")
1: .with(ANYTHING).will(throwException(
2: new UnexpectedRollbackException("")));
3: assertEquals(action.delete(), "input");