Como os testes de unidade facilitam o design?

43

Nosso colega promove a redação de testes de unidade como realmente nos ajudando a refinar nosso design e refatorar as coisas, mas não vejo como. Se eu estiver carregando um arquivo CSV e analisando-o, como o teste unitário (validando os valores nos campos) me ajudará a verificar meu design? Ele mencionou acoplamento e modularidade, etc., mas para mim não faz muito sentido - mas eu não tenho muito conhecimento teórico.

Isso não é o mesmo que a pergunta que você marcou como duplicado, eu estaria interessado em exemplos reais de como isso ajuda, não apenas a teoria dizendo "isso ajuda". Eu gosto da resposta abaixo e do comentário, mas gostaria de saber mais.

    
por User039402 09.02.2017 / 19:12
fonte

10 respostas

2

Não só os testes de unidade facilitam o design, mas esse é um dos seus principais benefícios.

Escrever teste primeiro expulsa a modularidade e limpa a estrutura do código.

Quando você escreve seu código de teste em primeiro lugar, você verá que quaisquer "condições" de uma dada unidade de código são naturalmente enviadas para dependências (geralmente através de mocks ou stubs) quando você as assume em seu código.

"Dada a condição x, esperar comportamento y", muitas vezes se tornará um stub para fornecer x (que é um cenário no qual o teste precisa verificar o comportamento do componente atual) e y se tornará uma simulação, uma chamada para a qual será verificada no final do teste (a menos que seja um "deve retornar y ", caso em que o teste apenas verificará o valor de retorno explicitamente).

Então, uma vez que esta unidade se comporte como especificado, você passa a escrever as dependências (para x e y ) que você descobriu.

Isso torna a escrita de código limpo e modular um processo muito fácil e natural, onde, de outro modo, é fácil desfocar responsabilidades e acoplar comportamentos juntos sem perceber.

Escrever testes mais tarde dirá quando seu código estiver mal estruturado.

Quando escrever testes para um pedaço de código se torna difícil porque há muitas coisas para esboçar ou escarnecer, ou porque as coisas estão muito juntas, você sabe que tem melhorias para fazer em seu código.

Quando "alterar testes" se torna um fardo porque há muitos comportamentos em uma única unidade, você sabe que tem melhorias a serem feitas em seu código (ou simplesmente em sua abordagem para escrever testes - mas esse não é o caso em geral). minha experiência).

Quando seus cenários se tornam muito complicados ("se x e y e z then ...") porque você precisa abstrair mais, você sabe que tem melhorias para fazer no seu código.

Quando você acaba com os mesmos testes em dois equipamentos diferentes devido à duplicação e à redundância, sabe que há melhorias a serem feitas em seu código.

Aqui está uma excelente palestra de Michael Feathers demonstrando a estreita relação entre testabilidade e design em código (originalmente postado por displayName nos comentários). A palestra também aborda algumas queixas e conceitos errôneos sobre o bom design e testabilidade em geral.

    
por 11.02.2017 / 10:45
fonte
103

O melhor dos testes unitários é que eles permitem que você use seu código como outros programadores usarão seu código.

Se o seu código é complicado para o teste unitário, provavelmente será difícil usá-lo. Se você não puder injetar dependências sem passar por aros, seu código provavelmente será inflexível de usar. E se você precisar gastar muito tempo configurando dados ou descobrir em que ordem fazer as coisas, seu código em teste provavelmente terá muito acoplamento e será difícil trabalhar com ele.

    
por 09.02.2017 / 19:15
fonte
31

Demorei um pouco para perceber, mas o benefício real (editar: para mim, sua milhagem pode variar) de fazer desenvolvimento orientado a testes ( usando testes unitários) é que você tem que fazer o design da API na frente !

Uma abordagem típica para o desenvolvimento é descobrir primeiro como resolver um determinado problema e, com esse conhecimento e o projeto de implementação inicial, alguma maneira de invocar sua solução. Isso pode dar alguns resultados bastante interessantes.

Ao fazer TDD você precisa escrever o código que irá usar sua solução. Parâmetros de entrada e saída esperada para que você possa garantir que está certo. Isso, por sua vez, exige que você descubra o que realmente precisa fazer, para poder criar testes significativos. Então, e só então, você implementa a solução. Também é minha experiência que quando você sabe exatamente o que seu código deve alcançar, fica mais claro.

Em seguida, depois que os testes da unidade de implementação o ajudarem a garantir que a refatoração não interrompa a funcionalidade e forneça documentação sobre como usar seu código (que você sabe que está certo quando o teste foi aprovado!). Mas estes são secundários - o maior benefício é a mentalidade na criação do código em primeiro lugar.

    
por 10.02.2017 / 00:09
fonte
6

Eu concordaria que 100% dos testes unitários ajudam "nos ajuda a refinar nosso design e refatorar as coisas".

Tenho duas dúvidas sobre se eles ajudam você a fazer o design inicial . Sim, eles revelam falhas óbvias e o forçam a pensar em "como posso tornar o código testável"? Isso deve levar a menos efeitos colaterais, configurações e configurações mais fáceis, etc.

No entanto, na minha experiência, testes unitários excessivamente simplistas, escritos antes de você realmente entender o que o design deveria ser (admitidamente, isso é um exagero do TDD radical, mas muitas vezes os codificadores escrevem um teste antes de pensarem muito) levar a modelos de domínio anêmico que expõem internals demais.

Minha experiência com o TDD foi há vários anos, por isso estou interessado em saber quais técnicas mais novas podem ajudar a escrever testes que não influenciam muito o design subjacente. Obrigado.

    
por 10.02.2017 / 01:20
fonte
5

O teste de unidade permite que você veja como funcionam as interfaces entre as funções e geralmente fornece informações sobre como melhorar o design local e o design geral. Além disso, se você desenvolver seus testes de unidade enquanto desenvolve seu código, você tem um conjunto de testes de regressão pronto. Não importa se você está desenvolvendo uma interface do usuário ou uma biblioteca de back-end.

Uma vez que o programa é desenvolvido (com testes de unidade), como os erros são descobertos, você pode adicionar testes para confirmar que os erros foram corrigidos.

Eu uso o TDD para alguns dos meus projetos. Eu me dediquei muito a elaborar exemplos que extraí de livros didáticos ou de artigos considerados corretos e testar o código que estou desenvolvendo usando esses exemplos. Qualquer mal-entendido que eu tenha sobre os métodos se torna muito aparente.

Eu costumo ser um pouco mais solto do que alguns de meus colegas, já que eu não me importo se o código é escrito primeiro ou o teste é escrito primeiro.

    
por 09.02.2017 / 19:57
fonte
5

Quando você quiser testar o seu valor de detecção do analisador adequadamente, convém passar uma linha de um arquivo CSV. Para tornar seu teste direto e curto, você pode querer testá-lo através de um método que aceite uma linha.

Isso automaticamente fará com que você separe a leitura das linhas da leitura de valores individuais.

Em outro nível, talvez você não queira colocar todos os tipos de arquivos CSV físicos em seu projeto de teste, mas faça algo mais legível, declarando uma grande string CSV dentro de seu teste para melhorar a legibilidade e a intenção do teste. Isso levará você a dissociar o analisador de qualquer E / S que você faria em outro lugar.

Apenas um exemplo básico, apenas comece a praticar, você sentirá a magia em algum momento (eu tenho).

    
por 09.02.2017 / 20:28
fonte
3

Simplificando, escrever testes de unidade ajuda a expor as falhas em seu código.

Este espetacular guia para escrever código testável , escrito por Jonathan Wolter, Russ Ruffer e Miško Hevery, contém numerosos exemplos de como falhas no código, que por acaso inibem os testes, também impedem a fácil reutilização e flexibilidade do mesmo código. Assim, se seu código é testável, é mais fácil de usar. A maioria das "morais" são dicas ridiculamente simples que melhoram muito o design de código ( Injeção de Dependência FTW).

Por exemplo: É muito difícil testar se o método computeStuff opera corretamente quando o cache começa a despejar coisas. Isso é porque você tem que adicionar manualmente porcaria no cache até que o "bigCache" esteja quase cheio.

public OopsIHardcoded {

   Cache cacheOfExpensiveComputations;

   OopsIHardcoded() {
       this.cacheOfExpensiveComputation = buildBigCache();
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

No entanto, quando usamos injeção de dependência, é muito mais fácil testar se o método computeStuff opera corretamente quando o cache começa a despejar coisas. Tudo o que fazemos é criar um teste no qual chamamos new HereIUseDI(buildSmallCache()); Notice, temos mais controle nuançado do objeto e ele paga dividendos imediatamente.

public HereIUseDI {

   Cache cacheOfExpensiveComputations;

   HereIUseDI(Cache cache) {
       this.cacheOfExpensiveComputation = cache;
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

Benefícios similares podem ser obtidos quando nosso código requer dados que normalmente são mantidos em um banco de dados ... apenas passe EXATAMENTE os dados que você precisa.

    
por 09.02.2017 / 21:41
fonte
0

Dependendo do que se entende por 'Testes de Unidade', eu não acho que os testes Unit de baixo nível facilitam um bom design, tanto quanto testes de integração nível ligeiramente mais altos - testes que testam que um grupo de atores (classes, funções, qualquer que seja) em seu código se combinam adequadamente para produzir um grande número de comportamentos desejáveis que foram acordados entre a equipe de desenvolvimento e o proprietário do produto.

Se você pode escrever testes nesses níveis, isso o leva a criar um código bom, lógico e semelhante a API, que não requer muitas dependências loucas - o desejo de ter uma configuração de teste simples naturalmente o levará a não tem muitas dependências loucas ou código strongmente acoplado.

Não se engane, porém - Testes de unidade podem levar a um design ruim, bem como a um bom design . Eu vi desenvolvedores pegarem um código que já tem um bom design lógico e uma única preocupação, e separá-lo e introduzir mais interfaces puramente com o propósito de testar, e como resultado tornar o código menos legível e mais difícil de mudar , bem como possivelmente ter mais bugs se o desenvolvedor tiver decidido que ter muitos testes de unidade de baixo nível significa que eles não precisam ter testes de nível mais alto. Um exemplo favorito é um bug que eu consertei onde havia um código muito 'quebrado', testável, relacionado à obtenção de informações dentro e fora da área de transferência. Todos quebrados e desacoplados a níveis muito pequenos de detalhes, com muitas interfaces, muitos truques nos testes e outras coisas divertidas. Apenas um problema - não havia nenhum código que realmente interagisse com o mecanismo de área de transferência do sistema operacional, portanto, o código em produção realmente não fazia nada.

Os testes de unidade podem impulsionar seu design, mas eles não o guiam automaticamente para um bom design. Você precisa ter ideias sobre o bom design que vai além de "esse código é testado, portanto, é testável, portanto é bom '.

É claro que se você é uma daquelas pessoas para as quais 'testes de unidade' significam 'quaisquer testes automatizados que não sejam direcionados pela interface do usuário', alguns desses avisos podem não ser tão relevantes - como eu disse, acho que esses testes de integração de nível mais alto costumam ser os mais úteis quando se trata de impulsionar seu design.

    
por 11.02.2017 / 18:15
fonte
-2

Testes de unidade podem ajudar na refatoração quando o código novo passa por todos os testes antigos .

Digamos que você tenha implementado um bubblesort porque estava com pressa e não preocupado com o desempenho, mas agora deseja um quicksort porque os dados estão ficando mais longos. Se todos os testes passarem, as coisas parecem boas.

É claro que os testes precisam ser abrangentes para que isso funcione. No meu exemplo, seus testes podem não cobrir estabilidade porque isso não era uma preocupação com o bubblesort.

    
por 11.02.2017 / 13:16
fonte
-3

Eu achei os testes de unidade mais valiosos para facilitar a manutenção de longo prazo de um projeto. Quando volto a um projeto depois de meses e não lembro de muitos detalhes, executar testes me impede de quebrar coisas.

    
por 10.02.2017 / 00:45
fonte