É normal gastar tanto, se não mais, tempo escrevendo testes do que o código real?

211

Acho os testes muito mais difíceis e difíceis de escrever do que o código real que eles estão testando. Não é incomum para mim gastar mais tempo escrevendo o teste do que o código que está testando.

Isso é normal ou estou fazendo algo errado?

As perguntas “ O teste de unidade ou o desenvolvimento orientado a testes vale a pena? ”,“ Estamos gastando mais tempo implementando testes funcionais do que implementando o próprio sistema, isso é normal? ”E suas respostas são mais sobre se vale a pena testar (como em" devemos pular os testes de escrita? "). Embora eu esteja convencido de que os testes são importantes, estou me perguntando se passo mais tempo em testes do que o código real é normal ou se sou apenas eu.

A julgar pelo número de visualizações, respostas e votos positivos da minha pergunta recebida, só posso assumir que é uma preocupação legítima que não é abordada em nenhuma outra pergunta no site.

carregado com mola
fonte
20
Anedótico, mas acho que passo tanto tempo escrevendo testes quanto escrevendo código quando TDD. É quando deslizo e escrevo testes depois de passar mais tempo nos testes do que no código.
RubberDuck
10
Você também gasta mais tempo lendo do que escrevendo seu código.
Thorbjørn Ravn Andersen
27
Além disso, os testes são um código real. Você simplesmente não envia essa peça aos clientes.
Thorbjørn Ravn Andersen
5
Idealmente, você gastará mais tempo executando do que escrevendo seu código também. (Caso contrário, você tinha acabado de fazer a tarefa à mão.)
Joshua Taylor
5
@RubberDuck: Experiência oposta aqui. Às vezes, quando escrevo testes após o fato do código e do design já estarem bem organizados, não preciso reescrever o código e os testes demais. Portanto, leva menos tempo para escrever os testes. Não é uma regra, mas acontece comigo com bastante frequência.
Giorgio

Respostas:

205

Lembro-me de um curso de engenharia de software que um gasta ~ 10% do tempo de desenvolvimento escrevendo um novo código e os outros 90% são depuração, teste e documentação.

Como os testes de unidade capturam a depuração e o esforço de teste em código (potencialmente automatizado), faria sentido que mais esforço fosse neles; o tempo real gasto não deve ser muito maior do que a depuração e o teste que seria necessário sem a gravação dos testes.

Finalmente, os testes também devem servir como documentação! Deve-se escrever testes de unidade da maneira que o código se destina a ser usado; ou seja, os testes (e o uso) devem ser simples, coloque as coisas complicadas na implementação.

Se for difícil escrever seus testes, o código que eles testam provavelmente será difícil de usar!

esoterik
fonte
4
Pode ser um bom momento para analisar por que o código é tão difícil de testar :) Tente "desenrolar" linhas multifuncionais complexas que não são estritamente necessárias, como operadores binários / ternários aninhados em massa ... Eu realmente odeio binário desnecessário / operador ternário, que também tem um operador binário / ternário como um dos caminhos ...
Nelson
53
Eu imploro para discordar da última parte. Se você deseja obter uma cobertura de teste de unidade muito alta, precisará cobrir casos de uso raros e às vezes diretos contra o uso pretendido do seu código. Escrever testes para os casos de canto pode ser apenas a parte que consome mais tempo de toda a tarefa.
Otto 14/10
Eu já disse isso em outro lugar, mas o teste de unidade tende a demorar muito mais, porque a maioria dos códigos tende a seguir uma espécie de padrão "Princípio de Pareto": você pode cobrir cerca de 80% da sua lógica com cerca de 20% do código necessário para cubra 100% de sua lógica (ou seja, cobrir todos os casos extremos leva cerca de cinco vezes mais código de teste de unidade). Obviamente, dependendo da estrutura, você pode inicializar o ambiente para vários testes, reduzindo o código geral necessário, mas mesmo isso exige um planejamento adicional. Chegar a 100% de confiança requer muito mais tempo do que simplesmente testar os caminhos principais.
Phyrfox 14/10/2015
2
@ phyrfox Eu acho isso muito cauteloso, é mais como "os outros 99% do código são casos extremos". O que significa que os outros 99% dos testes são para esses casos extremos.
Móż 14/10/2015
@ Nelson Eu concordo que operadores ternários aninhados são difíceis de ler, mas não acho que eles tornem os testes particularmente difíceis (uma boa ferramenta de cobertura informará se você perdeu uma das combinações possíveis). Na IMO, é difícil testar o software quando está muito acoplado ou depende de dados conectados ou dados não passados ​​como parâmetro (por exemplo, quando uma condição depende da hora atual e isso não é passado como parâmetro). Isso não está diretamente relacionado ao quão "legível" o código é, embora, é claro, todas as outras coisas iguais, o código legível seja melhor!
Andres F.
96

Isto é.

Mesmo se você fizer apenas testes de unidade, não é incomum ter mais código nos testes do que o código realmente testado. Não há nada de errado com isso.

Considere um código simples:

public void SayHello(string personName)
{
    if (personName == null) throw new NullArgumentException("personName");

    Console.WriteLine("Hello, {0}!", personName);
}

Quais seriam os testes? Existem pelo menos quatro casos simples para testar aqui:

  1. O nome da pessoa é null. A exceção é realmente lançada? São pelo menos três linhas de código de teste para escrever.

  2. O nome da pessoa é "Jeff". Nós recebemos uma "Hello, Jeff!"resposta? São quatro linhas de código de teste.

  3. O nome da pessoa é uma sequência vazia. Que resultado esperamos? Qual é a saída real? Pergunta secundária: corresponde aos requisitos funcionais? Isso significa outras quatro linhas de código para o teste de unidade.

  4. O nome da pessoa é curto o suficiente para uma sequência, mas muito longo para ser combinado com "Hello, "o ponto de exclamação. O que acontece?

Isso requer muito código de teste. Além disso, as partes mais elementares do código geralmente exigem código de configuração que inicializa os objetos necessários para o código em teste, o que também leva a escrever stubs e zombarias, etc.

Se a proporção for muito grande, nesse caso, você pode verificar algumas coisas:

  • Existe duplicação de código nos testes? O fato de ser um código de teste não significa que o código deve ser duplicado (cópia-colado) entre testes semelhantes: essa duplicação dificultará a manutenção desses testes.

  • Existem testes redundantes? Como regra geral, se você remover um teste de unidade, a cobertura da filial deverá diminuir. Caso contrário, isso pode indicar que o teste não é necessário, pois os caminhos já estão cobertos por outros testes.

  • Você está testando apenas o código que deve testar? Não é esperado que você teste a estrutura subjacente das bibliotecas de terceiros, mas exclusivamente o código do próprio projeto.

Com testes de fumaça, testes de sistema e integração, testes funcionais e de aceitação e testes de estresse e carga, você adiciona ainda mais código de teste, portanto, não é de se preocupar que você tenha quatro ou cinco LOC de testes para cada LOC do código real.

Uma observação sobre TDD

Se você estiver preocupado com o tempo que leva para testar seu código, pode ser que você esteja fazendo errado, ou seja, primeiro o código, testes depois. Nesse caso, o TDD pode ajudar incentivando você a trabalhar em iterações de 15 a 45 segundos, alternando entre código e testes. De acordo com os defensores do TDD, ele acelera o processo de desenvolvimento, reduzindo o número de testes que você precisa fazer e, mais importante, a quantidade de código comercial a ser escrito e especialmente reescrito para teste.


¹ Deixe n ser o comprimento máximo de uma cadeia de caracteres . Podemos chamar SayHelloe passar por referência uma cadeia de comprimento n -1 que deve funcionar perfeitamente. Agora, na Console.WriteLineetapa, a formatação deve terminar com uma sequência de comprimento n + 8, que resultará em uma exceção. Possivelmente, devido aos limites de memória, mesmo uma string contendo n / 2 caracteres levará a uma exceção. A pergunta que se deve fazer é se esse quarto teste é um teste de unidade (parece um, mas pode ter um impacto muito maior em termos de recursos em comparação com os testes de unidade médios) e se ele testa o código real ou a estrutura subjacente.

Arseni Mourzenko
fonte
5
Não esqueça que uma pessoa também pode ter o nome nulo. stackoverflow.com/questions/4456438/…
psatek 14/10
11
@JacobRaihle Suponho que @MainMa significa o valor dos personNameajustes em a string, mas o valor de personNamemais os valores concatenados excedem string.
Woz
@ JacobRaihle: Eu editei minha resposta para explicar esse ponto. Veja a nota de rodapé.
Arseni Mourzenko
4
As a rule of thumb, if you remove a unit test, the branch coverage should decrease.Se eu escrever todos os quatro testes mencionados acima e remover o terceiro teste, a cobertura diminuirá?
Vivek
3
"tempo suficiente" → "tempo demais" (no ponto 4)?
Paŭlo Ebermann 14/10
59

Eu acho que é importante distinguir entre dois tipos de estratégias de teste: teste de unidade e teste de integração / aceitação.

Embora o teste de unidade seja necessário em alguns casos, muitas vezes é irremediavelmente superado. Isso é exacerbado por métricas sem sentido impostas aos desenvolvedores, como "100% de cobertura". http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf fornece um argumento convincente para isso. Considere os seguintes problemas com testes de unidade agressivos:

  • Uma abundância de testes inúteis que não documentam um valor comercial, mas simplesmente existem para se aproximar dessa cobertura de 100%. Onde trabalho, temos que escrever testes de unidade para fábricas que não fazem mais nada além de criar novas instâncias de classes. Não agrega valor. Ou os longos métodos .equals () gerados pelo Eclipse - não há necessidade de testá-los.
  • Para facilitar o teste, os desenvolvedores subdividem algoritmos complexos em unidades testáveis ​​menores. Parece uma vitória, certo? Não se você precisar ter 12 classes abertas para seguir um caminho de código comum. Nesses casos, o teste de unidade pode realmente diminuir a legibilidade do código. Outro problema é que, se você dividir seu código em pedaços muito pequenos, você acabará com uma magnitude de classes (ou pedaços de código) que parecem não ter justificativa além de serem um subconjunto de outro pedaço de código.
  • Refatoração de código altamente coveraged pode ser difícil, como você também precisa manter a abundância de testes de unidade que dependem dele trabalhando apenas assim . Isso é agravado pelos testes de unidade comportamental, nos quais parte do seu teste também verifica a interação dos colaboradores de uma classe (geralmente zombados).

Os testes de integração / aceitação, por outro lado, são uma parte extremamente importante da qualidade do software e, na minha experiência, você deve gastar uma quantidade significativa de tempo para corrigi-los.

Muitas lojas consumiram o kool-aid TDD, mas, como mostra o link acima, vários estudos mostram que seu benefício é inconclusivo.

firtydank
fonte
10
+1 para o ponto de refatoração. Eu costumava trabalhar em um produto legado que foi corrigido e compactado por mais de uma década. Apenas tentar determinar as dependências de um método específico pode levar a maior parte de um dia e, em seguida, tentar descobrir como zombar delas pode demorar ainda mais. Não era incomum uma alteração de cinco linhas exigir mais de 200 linhas de código de teste e levar a maior parte de uma semana para ser feita.
TMN
3
Este. Na resposta da MainMa, o teste 4 não deve ser feito (fora de um contexto acadêmico), porque pense em como isso ocorreria na prática ... se o nome de uma pessoa estiver próximo do tamanho máximo de uma string, algo deu errado. Não teste, na maioria dos casos não possui um caminho de código para detectá-lo. A resposta apropriada é permitir que a estrutura lance a exceção de falta de memória subjacente, porque é esse o problema real.
Móż 14/10/2015
3
Estou torcendo por você até que "não seja necessário testar esses .equals()métodos longos gerados pelo Eclipse". Eu escrevi um equipamento de teste para equals()e compareTo() github.com/GlenKPeterson/TestUtils Quase todas as implementações que eu já testei estavam ausentes. Como você usa coleções se equals()e hashCode()não funcionam em conjunto de forma correcta e eficiente? Estou torcendo novamente pelo resto da sua resposta e votei-a novamente. Eu até concordo que alguns métodos equals () gerados automaticamente podem não precisar de testes, mas eu tive tantos bugs com implementações ruins que isso me deixa nervoso.
GlenPeterson
11
@GlenPeterson Concordo. Um colega meu escreveu o EqualsVerifier para esse fim. Veja github.com/jqno/equalsverifier
Tohnmeister
@ Não, você ainda precisa testar entradas inaceitáveis, é assim que as pessoas encontram explorações de segurança.
precisa
11

Não pode ser generalizado.

Se eu precisar implementar uma fórmula ou algoritmo a partir de renderização com base física, pode muito bem ser que eu passe 10 horas em testes de unidade paranóicos, pois sei que o menor bug ou imprecisão pode levar a erros quase impossíveis de diagnosticar, meses depois .

Se eu apenas quiser agrupar logicamente algumas linhas de código e atribuir um nome a ele, usado apenas no escopo do arquivo, talvez não seja possível testá-lo (se você insistir em escrever testes para todas as funções, sem exceção, os programadores poderão recuar para escrever o menor número possível de funções).

phresnel
fonte
Este é um ponto de vista realmente valioso. A pergunta precisa de mais contexto para ser respondida completamente.
CLF
3

Sim, é normal se você estiver falando sobre TDDing. Quando você realiza testes automatizados, protege o comportamento desejado do seu código. Ao escrever seus testes primeiro, você determina se o código existente já possui o comportamento desejado.

Isso significa que:

  • Se você escrever um teste que falhar, a correção do código com a coisa mais simples que funciona é mais curta do que a gravação do teste.
  • Se você escrever um teste que passa, não possui código extra para escrever, gastando mais tempo efetivamente para escrever um teste do que o código.

(Isso não leva em consideração a refatoração de código, que visa gastar menos tempo escrevendo o código subsequente. É equilibrado pela refatoração de teste, que visa gastar menos tempo escrevendo testes subsequentes.)

Sim também se você estiver falando sobre escrever testes após o fato, estará gastando mais tempo:

  • Determinando o comportamento desejado.
  • Determinando maneiras de testar o comportamento desejado.
  • Cumprindo dependências de código para poder escrever os testes.
  • Corrigindo código para testes que falham.

Então você gasta realmente escrevendo código.

Então, sim, é uma medida esperada.

Laurent LA RIZZA
fonte
3

Acho que é a parte mais importante.

O teste de unidade nem sempre é sobre "ver se funciona corretamente", é sobre aprender. Depois de testar algo o suficiente, ele fica "codificado" no cérebro e, eventualmente, você diminui o tempo de teste da unidade e pode começar a escrever classes e métodos inteiros sem testar nada até terminar.

É por isso que uma das outras respostas nesta página menciona que, em um "curso", eles fizeram 90% dos testes, porque todos precisavam aprender as advertências de seus objetivos.

O teste de unidade não é apenas um uso muito valioso do seu tempo, pois literalmente aprimora suas habilidades, é uma boa maneira de revisar seu próprio código novamente e encontrar um erro lógico ao longo do caminho.

Jesse
fonte
2

Pode ser para muitas pessoas, mas depende.

Se você estiver escrevendo os testes primeiro (TDD), poderá estar enfrentando alguma sobreposição no tempo gasto escrevendo o teste, na verdade, é útil escrever o código. Considerar:

  • Determinando resultados e entradas (parâmetros)
  • Convenções de nomenclatura
  • Estrutura - onde colocar as coisas.
  • Pensamento antigo

Ao escrever testes depois de escrever o código, você pode descobrir que seu código não é facilmente testável; portanto, escrever testes é mais difícil / leva mais tempo.

A maioria dos programadores está escrevendo código há muito mais tempo que os testes, portanto, espero que a maioria deles não seja tão fluente. Além disso, você adiciona o tempo necessário para entender e utilizar sua estrutura de teste.

Acho que precisamos mudar nossa mentalidade sobre quanto tempo leva para codificar e como o teste de unidade está envolvido. Nunca olhe para ele a curto prazo e nunca compare o tempo total para fornecer um recurso específico, porque você deve considerar não apenas que está escrevendo um código melhor / menos com erros, mas um código que é mais fácil de alterar e ainda o torna melhor / menos buggy.

Em algum momento, todos somos capazes de escrever código tão bom, então algumas das ferramentas e técnicas podem oferecer muito apenas para melhorar nossas habilidades. Não é como se eu pudesse construir uma casa se tivesse apenas uma serra guiada a laser.

JeffO
fonte
2

É normal gastar tanto, se não mais, tempo escrevendo testes do que o código real?

Sim. Com algumas ressalvas.

Antes de tudo, é "normal" no sentido de que a maioria das grandes lojas funciona dessa maneira, portanto, mesmo que essa maneira tenha sido completamente mal orientada e burra, ainda assim, o fato de a maioria das grandes lojas funcionar dessa maneira a torna "normal".

Com isso, não pretendo sugerir que testá-lo esteja errado. Eu trabalhei em ambientes sem teste e em ambientes com teste obsessivo-compulsivo, e ainda posso lhe dizer que mesmo o teste obsessivo-compulsivo foi melhor do que nenhum teste.

E eu ainda não faço TDD (quem sabe, eu posso no futuro), mas faço a grande maioria dos meus ciclos de edição, execução e depuração executando os testes, não o aplicativo real, então, naturalmente, trabalho muito nos meus testes, para evitar ao máximo a execução do aplicativo real.

No entanto, esteja ciente de que há perigos em testes excessivos e, especificamente, na quantidade de tempo gasto na manutenção dos testes. (Estou escrevendo principalmente esta resposta para apontar isso especificamente.)

No prefácio de The Art of Unit Testing (Manning, 2009), de Roy Osherove , o autor admite ter participado de um projeto que falhou em grande parte devido à enorme carga de desenvolvimento imposta por testes de unidade mal projetados que tiveram que ser mantidos durante todo o período. duração do esforço de desenvolvimento. Portanto, se você passar muito tempo sem fazer nada além de manter seus testes, isso não significa necessariamente que você está no caminho certo, porque é "normal". Seu esforço de desenvolvimento pode ter entrado em um modo não íntegro, onde pode ser necessário repensar radicalmente sua metodologia de teste para salvar o projeto.

Mike Nakis
fonte
0

É normal gastar tanto, se não mais, tempo escrevendo testes do que o código real?

  • Sim para escrever testes de unidade (teste um módulo isoladamente) se o código estiver altamente acoplado (herdado, não há separação suficiente de preocupações , falta de injeção de dependência , não foi desenvolvido o tdd )
  • Sim para escrever testes de integração / aceitação se a lógica a ser testada puder ser alcançada apenas via código GUI
  • Não para escrever testes de integração / aceitação, desde que o código da GUI e a lógica de negócios estejam separados (o teste não precisa interagir com a GUI)
  • Não para escrever testes de unidade se houver uma série de preocupações, injeção de dependência, o código foi testdrive desenvolvido (tdd)
k3b
fonte