Do ponto de vista do valor, vejo dois grupos de testes de unidade em minha prática:
- Testes que testam alguma lógica não trivial. Escrevê-los (antes da implementação ou depois) revela alguns problemas / possíveis erros e ajuda a ter confiança caso a lógica seja alterada no futuro.
- Testes que testam alguma lógica muito trivial. Esses testes são mais parecidos com códigos de documentos (geralmente com zombarias) do que com testes. O fluxo de trabalho de manutenção desses testes não é "alguma lógica alterada, teste ficou vermelho - graças a Deus eu escrevi esse teste", mas "algum código trivial mudou, teste tornou-se falso negativo - eu tenho que manter (reescrever) o teste sem obter lucro" . Na maioria das vezes, esses testes não valem a pena ser mantidos (exceto razões religiosas). E, de acordo com minha experiência em muitos sistemas, esses testes são como 80% de todos os testes.
Estou tentando descobrir o que os outros caras pensam sobre o tema dos testes de unidade de separação por valor e como isso corresponde à minha separação. Mas o que eu mais vejo é propaganda TDD em tempo integral ou propaganda de testes são inúteis, basta escrever o código. Estou interessado em algo no meio. Seus próprios pensamentos ou referências a artigos / papéis / livros são bem-vindos.
unit-testing
tdd
SiberianGuy
fonte
fonte
Respostas:
Eu acho que é natural encontrar uma divisão dentro dos testes de unidade. Existem muitas opiniões diferentes sobre como fazê-lo corretamente e, naturalmente, todas as outras opiniões estão inerentemente erradas . Recentemente, existem alguns artigos sobre o DrDobbs que exploram esse mesmo problema ao qual eu vinculo no final da minha resposta.
O primeiro problema que vejo nos testes é que é fácil cometer erros. Na aula de C ++ da minha faculdade, fomos expostos a testes de unidade no primeiro e no segundo semestre. Não sabíamos nada sobre programação em geral nos dois semestres - estávamos tentando aprender os fundamentos da programação via C ++. Agora imagine dizer aos alunos: "Ah, ei, você escreveu uma pequena calculadora anual de impostos! Agora escreva alguns testes de unidade para garantir que funcione corretamente". Os resultados devem ser óbvios - todos foram horríveis, incluindo minhas tentativas.
Depois de admitir que você é péssimo em escrever testes de unidade e deseja melhorar, em breve você se deparará com estilos modernos de teste ou diferentes metodologias. Ao testar metodologias, refiro-me a práticas como test-first ou o que Andrew Binstock da DrDobbs faz, que é escrever os testes ao lado do código. Ambos têm seus prós e contras e eu me recuso a entrar em detalhes subjetivos, porque isso incitará uma guerra de chamas. Se você não está confuso sobre qual metodologia de programação é melhor, talvez o estilo de teste faça o truque. Você deve usar TDD, BDD, testes baseados em propriedades? A JUnit possui conceitos avançados chamados Teorias que desfocam a linha entre TDD e testes baseados em propriedades. Qual usar quando?
tl; dr É fácil errar os testes, é incrivelmente opinativo e não acredito que qualquer metodologia de teste seja inerentemente melhor, desde que sejam usadas diligentemente e profissionalmente no contexto em que são apropriadas. Além disso, os testes são na minha opinião, uma extensão a asserções ou testes de sanidade que costumavam garantir uma abordagem ad-hoc rápida e rápida ao desenvolvimento, que agora é muito, muito mais fácil.
Para uma opinião subjetiva, prefiro escrever "fases" de testes, por falta de uma frase melhor. Escrevo testes de unidade que testam classes isoladamente, usando zombarias, quando necessário. Eles provavelmente serão executados com JUnit ou algo semelhante. Então escrevo testes de integração ou aceitação, que são executados separadamente e geralmente apenas algumas vezes por dia. Esse é o seu caso de uso não trivial. Eu normalmente uso o BDD, pois é bom expressar recursos em linguagem natural, algo que o JUnit não pode fornecer facilmente.
Por fim, recursos. Eles apresentarão opiniões conflitantes, principalmente centradas em testes de unidade em diferentes idiomas e com diferentes estruturas. Eles devem apresentar a divisão em ideologia e metodologia, permitindo que você faça sua própria opinião, desde que eu não tenha manipulado muito a sua :)
[1] A corrupção do ágil por Andrew Binstock
[2] Resposta às respostas do artigo anterior
[3] Resposta à corrupção do Agile pelo tio Bob
[4] Resposta à corrupção do Agile por Rob Myers
[5] Por que se preocupar com o teste de pepino?
[6] Você está enganando
[7] Afaste-se das ferramentas
[8] Comentário sobre 'Kata de algarismos romanos com comentário'
[9] Kata de algarismos romanos com comentários
fonte
Eu acredito que é importante ter testes de ambos os tipos e usá-los quando apropriado.
Como você disse, existem dois extremos e eu sinceramente também não concordo com nenhum deles.
A chave é que os testes de unidade precisam cobrir regras e requisitos de negócios . Se houver um requisito de que o sistema deve rastrear a idade de uma pessoa, escreva testes "triviais" para garantir que a idade seja um número inteiro não negativo. Você está testando o domínio de dados exigido pelo sistema: embora trivial, ele tem valor porque está aplicando os parâmetros do sistema .
Da mesma forma, com testes mais complexos, eles precisam agregar valor. Certamente, você pode escrever um teste que valide algo que não é um requisito, mas que deve ser aplicado em uma torre de marfim em algum lugar, mas esse é o tempo gasto melhor escrevendo testes que validam os requisitos pelos quais o cliente está pagando. Por exemplo, por que escrever um teste que valida seu código pode lidar com um fluxo de entrada que atinge o tempo limite, quando os únicos fluxos são de arquivos locais, não da rede?
Acredito firmemente em testes de unidade e uso o TDD onde quer que faça sentido. Os testes de unidade certamente agregam valor na forma de maior qualidade e comportamento "falham rápido" ao alterar o código. No entanto, há também a velha regra 80/20 . Em algum momento, você obterá retornos decrescentes ao escrever testes e precisará avançar para um trabalho mais produtivo, mesmo que exista algum valor mensurável ao escrever mais testes.
fonte
Aqui está minha opinião: todos os testes têm custos:
Também pretendemos que todos os testes forneçam benefícios (e, na minha experiência, quase todos os testes fornecem benefícios):
Portanto, é muito fácil ver que, se você escrever vários testes, eles provavelmente terão algum valor. O ponto em que isso fica complicado é quando você começa a comparar esse valor (que, a propósito, talvez você não saiba com antecedência - se você jogar fora seu código, os testes de regressão perdem o valor) com o custo.
Agora, seu tempo e esforço são limitados. Você gostaria de fazer as coisas que oferecem o maior benefício pelo menor custo. E acho que é uma coisa muito difícil de se fazer, principalmente porque pode exigir conhecimento que não se tem ou seria caro obter.
E esse é o verdadeiro atrito entre essas diferentes abordagens. Eu acredito que todos eles identificaram estratégias de teste que são benéficas. No entanto, cada estratégia tem custos e benefícios diferentes em geral. Além disso, os custos e benefícios de cada estratégia provavelmente dependerão fortemente das especificidades do projeto, do domínio e da equipe. Em outras palavras, pode haver várias melhores respostas.
Em alguns casos, aplicar código sem testes pode fornecer os melhores benefícios / custos. Em outros casos, um conjunto completo de testes pode ser melhor. Ainda em outros casos, melhorar o design pode ser a melhor coisa a fazer.
fonte
O que é um teste de unidade , realmente? E existe realmente uma grande dicotomia em jogo aqui?
Trabalhamos em um campo em que ler literalmente um pouco além do final de um buffer pode travar totalmente um programa, ou causar um resultado totalmente impreciso, ou conforme evidenciado pelo recente bug TLS "HeartBleed", coloca um sistema supostamente seguro aberto sem produzir nenhuma evidência direta da falha.
É impossível eliminar toda a complexidade desses sistemas. Mas nosso trabalho é, na medida do possível, minimizar e gerenciar essa complexidade.
Um teste de unidade é um teste que confirma, por exemplo, que uma reserva foi lançada com sucesso em três sistemas diferentes, uma entrada de log é criada e uma confirmação por email é enviada?
Eu vou dizer não . Esse é um teste de integração . E esses definitivamente têm seu lugar, mas também são um tópico diferente.
Um teste de integração funciona para confirmar a função geral de um "recurso" inteiro. Mas o código por trás desse recurso deve ser dividido em blocos de construção simples e testáveis, também conhecidos como "unidades".
Portanto, um teste de unidade deve ter um escopo muito limitado.
O que implica que o código testado pelo teste de unidade deve ter um escopo muito limitado.
O que implica ainda que um dos pilares do bom design é decompor seu problema complexo em partes menores e de propósito único (na medida do possível), que podem ser testadas em relativo isolamento um do outro.
O que você acaba criando é um sistema feito de componentes de base confiáveis e você sabe se alguma dessas unidades fundamentais de código quebra porque você escreveu testes simples, pequenos e de escopo limitado para dizer exatamente isso.
Em muitos casos, você provavelmente também deve ter vários testes por unidade. Os testes em si devem ser simples, testando um e apenas um comportamento na medida do possível.
A noção de um "teste de unidade" testando uma lógica complexa não trivial, elaborada é, penso eu, um pouco de oxímoro.
Portanto, se esse tipo de quebra de projeto deliberada ocorreu, como no mundo um teste de unidade poderia repentinamente começar a produzir falsos positivos, a menos que a função básica da unidade de código testada tenha mudado? E se isso aconteceu, é melhor você acreditar que existem alguns efeitos cascata não óbvios em jogo. Seu teste interrompido, que parece estar produzindo um falso positivo, na verdade está avisando que algumas alterações quebraram um círculo mais amplo de dependências na base de código e precisam ser examinadas e corrigidas.
Algumas dessas unidades (muitas delas) podem precisar ser testadas usando objetos simulados, mas isso não significa que você precise escrever testes mais complexos ou elaborados.
Voltando ao meu exemplo artificial de um sistema de reserva, você realmente não pode ser o envio de solicitações de folga para um banco de dados de reserva ao vivo ou serviço de terceiros (ou mesmo um exemplo "dev" dele) cada vez que você unidade de teste de seu código.
Então você usa zombarias que apresentam o mesmo contrato de interface. Os testes podem validar o comportamento de um pedaço de código determinístico relativamente pequeno. Verde todo o tabuleiro diz que os blocos que compõem sua fundação não estão quebrados.
Mas a lógica dos testes unitários individuais permanece a mais simples possível.
fonte
Obviamente, isso é apenas minha opinião, mas ter passado os últimos meses aprendendo programação funcional no fsharp (vindo de um background em C #) me fez perceber algumas coisas.
Como o OP afirmou, normalmente existem 2 tipos de "testes de unidade" que vemos no dia a dia. Testes que cobrem as entradas e saídas de um método, que geralmente são os mais valiosos, mas são difíceis de executar para 80% do sistema, que se refere menos a "algoritmos" e mais a "abstrações".
O outro tipo, é testar a interatividade da abstração, geralmente envolve zombaria. Na minha opinião, esses testes são principalmente necessários devido ao design do seu aplicativo. Omitindo-os, e você corre o risco de erros estranhos e código spagetti, porque as pessoas não pensam sobre seu design adequadamente, a menos que sejam forçadas a fazer os testes primeiro (e mesmo assim, geralmente estragam tudo). O problema não é tanto a metodologia de teste, mas o design subjacente do sistema. A maioria dos sistemas criados com linguagens imperativas ou OO tem uma dependência inerente aos "efeitos colaterais", também conhecidos como "Faça isso, mas não me diga nada". Quando você confia no efeito colateral, precisa testá-lo, porque um requisito ou operação comercial geralmente faz parte dele.
Quando você projeta seu sistema de uma maneira mais funcional, onde evita criar dependências de efeitos colaterais e evita alterações / rastreamento de estado através da imutabilidade, ele permite que você se concentre mais intensamente nos testes de entrada e saída, que testam claramente mais a ação e menos como você chega lá. Você ficará surpreso com o que coisas como imutabilidade podem oferecer em termos de soluções muito mais simples para os mesmos problemas e, quando você não estiver mais dependente de "efeitos colaterais", poderá executar ações como programação paralela e assíncrona sem quase nenhum custo adicional.
Desde que comecei a codificar no Fsharp, não precisava de uma estrutura de zombaria para nada e até perdi minha dependência completamente de um contêiner do IOC. Meus testes são conduzidos por necessidades e valores de negócios, e não em camadas de abstração pesada, normalmente necessárias para obter composição em programação imperativa.
fonte