Skip to content

Instantly share code, notes, and snippets.

@klauswuestefeld
Created September 14, 2011 05:02
Show Gist options
  • Save klauswuestefeld/1215891 to your computer and use it in GitHub Desktop.
Save klauswuestefeld/1215891 to your computer and use it in GitHub Desktop.
Transcendento Testes Unitários II
We couldn’t find any files to show.
@klauswuestefeld
Copy link
Author

klauswuestefeld commented Sep 14, 2011

Testes unitários são prejudiciais em alguns casos, como o abaixo, e, nesses casos*, não devem ser feitos.
.* Não quero ninguém saíndo por aí falando: "Nós nunca fazemos testes porque o Klaus falou q não precisa!" :P

Testes unitário trazem pelo menos cinco benefícios:

  1. Maior corretude (menos defeitos).
  2. Segurança para refatoração interna da unidade.
  3. Segurança para otimização interna da unidade.
  4. Guia para design.
  5. Exemplos para entendimento da unidade.

Esta é uma classe minha, q uso em produção, num sistema de deploy contínuo q fiz chamado Simploy:

public class SimployMainLoop {

    public SimployMainLoop(Deployer deployer, Trigger trigger) {
        deployer.deployGoodBuild();
        while (true) {
            trigger.waitFor();
            deployer.deployNewBuild();
        }
    }

}

Esse código é util? Ele pode ser considerado uma unidade?
Se sim, encontramos pelo menos um exemplo em que considero melhor NÃO fazer testes unitários.

Escreva, como exercício, os testes unitários para a classe acima. Dica: é necessário usar mocks. Considere:

1 - Corretude - Os testes q vc escreveu trouxeram algum ganho de corretude, algum ganho de cobertura que testes automatizados de integração ou funcionais já não trazem para uma unidade tão simples? Numa unidade tão ridiculamente simples, um bug não seria gritante aos olhos?
2 e 3 - Refatoração e Otimização - É questionável a necessidade de segurança para simplificar ou otimizar uma unidade que já está na sua forma mais simples e mais otimizada, na opinião de todos os que a estudaram (ou então sugira uma forma mais simples ou mais otimizada). Mesmo assim, a cobertura fornecida por testes de integração ou funcionais desta unidade já são segurança suficiente.
4 - Guia para Design - As vezes vemos uma solução de fora para dentro, as vezes de dentro para fora. Nesse último caso, não precisamos do teste para guiar o design. Se, porém, uma unidade simples dessas for descoberta de fora para dentro, usando teste unitário, na sequência o teste unitário deve ser APAGADO (motivos abaixo). Os testes de integração e funcionais já garantem q o design é usável para os clientes da unidade (mais, até, q o teste unitário).
5 - Exemplos para Entendimento - Os testes q vc escreveu tornam mais simples ou mais complicado entender o q faz a classe acima? Um novo colega não entenderia a classe mais rápido lendo ela diretamente, em vez de tentar decifrar as mockisses dos testes q vc escreveu?

Os custos de um teste unitário, são:

A) Escrever o teste a 1a vez.
B) Reler/Re-entender o teste a cada novo cara q precisa aprender ou relembrar aquela unidade.
C) Manutenção do teste toda vez q a unidade precisa ser alterada por:

  • Mudança de requisito.
  • Refatoração do sistema como um todo, realocando responsabilidades entre as unidades.

D) Tempo de execução do teste.

Escrever o teste a primeira vez (A) é investimento relativamente pequeno.
Tempo de execução (D) é insignificante num ambiente com bom controle de dependências, pq o teste unitário só precisa ser rodado, por definição, quando a unidade é alterada. Pra grande maioria das equipes, porém, que ainda não tem esse controle, o tempo de execução é super relevante.
Assim como ler um comentário óbvio de código, que deveria ter sido apagado, dá raiva perder tempo lendo um teste óbvio ou um teste que é >muito< mais complicado de entender q a unidade em si (B).

Por último, o mais importante é o seguinte: o custo de adaptar os testes unitários pra acompanhar mudanças nas unidades (C) é uma força que atua não só contra a produtividade (novos requisitos) mas também CONTRA A QUALIDADE do sistema, causando arrasto contra refatorações de sistema que precisam realocar responsabilidades em unidades diferentes.

Será que o código acima é exceção, raridade ou será que a maioria dos casos pode ser refatorada para unidades tão simples?

Repetindo: Não quero ninguém saindo por aí falando q nunca faz teste pq "o Klaus falou" q nao precisa.

Vc pode deixar de fazer teste unitário somente para as unidades que:

  • Forem tão ou mais simples q a acima. E
  • Tiverem cobertura de teste automatizado de integração ou funcional.

Falou, Klaus

@felipecruz
Copy link

Penso da mesma forma.. inclusive escrevi sobre isso também: http://www.loogica.net/blog/2010/09/21/testes-nao-obrigado/

@mauricioaniche
Copy link

Oi Klaus,

Chegando um pouco atrasado na discussão, mas vamos lá. Acho que temos que testar 100% do que escrevemos, mas precisamos sempre lembrar que testes de unidade não é o único nível de teste -- temos vários outros e devemos usar o que fizer mais sentido.

No código que você escreveu acima, também acho que testes de unidade não fazem sentido. Mas não pq esse código é simples; mas sim pq esse código simplesmente "controla" o fluxo das coisas. Ele não contém regras de negócio ou coisa do tipo. Um exemplo mais natural para a maioria dos desenvolvedores é justamente o código de um "controller" bem escrito. Ele basicamente invoca coisas em uma ordem determinada.

Se eu fizer um teste de unidade pra esses caras, no fim, vou testar se os métodos foram invocados na ordem que eu preciso. Não sei se vale a pena gastar tempo escrevendo isso (e é chato, pq é basicamente trabalhar com mocks). Acredito que um teste de integração/sistema me traria mais feedback sobre a qualidade do mesmo.

Agora, com certeza tuas classes Deployer e Trigger contém regras de negócio e a meu ver, muito provavelmente, deverão ser testadas de maneira isolada.

O que acha?

@juanplopes
Copy link

É tudo sobre contratos. Sua classe SimployMainLoop depende das implementações de Trigger e Deployer? Então por que as interfaces?

class FailTrigger implements Trigger {
    public void waitFor() {
        try {
            Thread.sleep(1 << 30);
        } catch (InterruptedException e) {
        }
    }
}

class FailDeployer implements Deployer {
    public void deployNewBuild() {
        throw new RuntimeException("gotta catch'em all");   
    }
}

@juanplopes
Copy link

Aliás, @felipecruz, acho que o @klauswuestefeld não quis dizer o que você escreveu no seu post:

"construir um sistema de informação com pouca lógica de negócio usando TDD não funciona"

@felipecruz
Copy link

eu vejo como uma critica ao 'over-testing'

e realmente.. TDD em um sistema basicamente de CRUD, nao faz sentido..

ps: editei um erro de port :)

@juanplopes
Copy link

Pelo que entendi, ele disse que podem existir partes de um sistema que não precisam de testes unitários.

Não disse que sistemas inteiros não precisam de testes unitários, nem que existem partes de sistema que não precisam de teste nenhum.

@felipecruz
Copy link

se vc faz um sistema de catalogo de filmes, apenas um catalogo, qual o sentido testar alguma coisa?

testar se o model, entity o que quer que seja é salvo no banco pelo ORM? testar se uma query "all" vem como todos os resultados?

se a unica regra de negocio de um catalogo é cadastrar, buscar, listar, editar e apagar, o que precisa ser testado?

existem trechos de codigo que nao precisam de testes.. assim como podem existir sistemas mais simples que nao precisam de testes.. Qual o sentido de tesdtar se o save do JPA ou do django realmente cria uma linha no banco? ele ja nao foi testado pelo proprio ORM?

e repara que ele ta falando de testes unitarios..

@juanlopes

queria saber de onde no blog voce tirou que eu disse isso

"Não disse que sistemas inteiros não precisam de testes unitários, nem que existem partes de sistema que não precisam de teste nenhum."

@klauswuestefeld
Copy link
Author

Agora, com certeza tuas classes Deployer e Trigger contém regras de negócio e a
meu ver, muito provavelmente, deverão ser testadas de maneira isolada. O que acha?

Hoje ainda faço testes unitários para muitas unidades, mas me pergunto se não faltou simplificá-las mais, a ponto da maioria das unidades não precisar mais de teste...

@klauswuestefeld
Copy link
Author

@juanlopes - Não entendi sua pergunta.

@velo
Copy link

velo commented Apr 19, 2013

@klauswuestefeld
Nesse caso acho que dá mesmo pra ficar sem testes.... mas... sempre o mas.... mas deployer.deployGoodBuild(), trigger.waitFor() e deployer.deployNewBuild() devem ter sidos testados em algum lugar.

Eu tenho uma situação que imagino ser similar ao que você descreve:

    public List<?> list(HttpServletRequest request) {
        return list( FilterFactory.newFilter( GivenSituationFilter.class, request ) );
    }

    protected List<?> list(GivenSituationFilter filter) {
        //logica de vedade
    }

Temos testes para o FilterFactory.newFilter() e testes para o list(GivenSituationFilter) protected. Agora o list(HttpServletRequest) public ficou sem testes.

(2 anos atrasado, kkkkk)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment