Como fazemos testes de unidade executados rapidamente?


40

Chegamos ao ponto em nosso projeto em que temos quase mil testes e as pessoas pararam de se preocupar em executá-los antes de fazer o check-in, porque leva muito tempo. Na melhor das hipóteses, eles executam os testes relevantes para o trecho de código que eles mudaram e, na pior das hipóteses, simplesmente fazem o check-in sem testar.

Acredito que esse problema se deva ao fato de a solução ter aumentado para 120 projetos (geralmente fazemos projetos muito menores e esta é apenas a segunda vez que fazemos o TDD corretamente) e o tempo de compilação + teste aumentou para cerca de dois a três minutos nas máquinas menores.

Como reduzimos o tempo de execução dos testes? Existem técnicas? Fingindo mais? Fingindo menos? Talvez os testes de integração maiores não devam ser executados automaticamente ao executar todos os testes?

Editar: como resposta a várias respostas, já usamos o CI e um servidor de compilação, é assim que sei que os testes falham. O problema (na verdade, um sintoma) é que continuamos recebendo mensagens sobre compilações com falha. A execução de testes parciais é algo que a maioria das pessoas faz, mas não todas. e em relação aos testes, eles são realmente muito bem feitos, usam falsificações para tudo e não há IO.


8
Obter um hardware melhor? O hardware é barato comparado ao tempo do programador.
Bryan Oakley

18
Você já sugeriu a solução em sua pergunta: execute apenas os testes relevantes para o trecho de código que foi alterado. Execute todo o conjunto de testes periodicamente, como parte do ciclo de controle de qualidade / liberação. Dito isso, 2 a 3 minutos não parecem demorar muito, então é possível que sua equipe de desenvolvedores faça o check-in com muita frequência.
Robert Harvey

3
Primeira referência, para descobrir de onde vem o custo de desempenho. Existem alguns testes caros ou é a grande quantidade de testes? Certas configurações são caras?
código é o seguinte

13
Porra, eu gostaria que nossos testes fossem apenas 2-3 minutos. Para executar todos os nossos testes de unidade, leva 25 minutos - e ainda não temos nenhum teste de integração.
precisa saber é o seguinte

4
2 a 3 minutos? Eita. O nosso pode funcionar horas ...
Roddy das ervilhas congeladas

Respostas:


51

Uma solução possível seria mover a parte de teste das máquinas de desenvolvimento para uma configuração de integração contínua ( Jenkins, por exemplo), usando um software de controle de versão de algum sabor ( git , svn , etc ...).

Quando um novo código tiver que ser escrito, o desenvolvedor especificado criará uma ramificação para o que estiver fazendo no repositório. Todo o trabalho será realizado neste ramo e eles poderão confirmar suas alterações no ramo a qualquer momento, sem prejudicar a linha principal de código.

Quando o recurso fornecido, a correção de bug ou o que quer que esteja trabalhando, é concluído que a ramificação pode ser mesclada de volta ao tronco (ou como você preferir) onde todos os testes de unidade são executados. Se um teste falhar, a mesclagem será rejeitada e o desenvolvedor será notificado para que eles possam corrigir os erros.

Você também pode fazer com que o servidor de IC execute os testes de unidade em cada ramificação de recurso à medida que as confirmações são feitas. Dessa forma, o desenvolvedor pode fazer algumas alterações, confirmar o código e permitir que o servidor execute os testes em segundo plano enquanto continua trabalhando em alterações adicionais ou outros projetos.

Um ótimo guia para uma maneira de fazer essa configuração pode ser encontrado aqui (específico ao git, mas deve funcionar para outros sistemas de controle de versão): http://nvie.com/posts/a-successful-git-branching-model/


15
Este. Se os desenvolvedores "pararam de se preocupar com executá-los (os testes de unidade) antes de fazer um check-in", então você quer sua configuração CI a ser executá-los após um check-in.
Carson63000

+1: Uma melhoria adicional seria modularizar os testes. Se um módulo / arquivo específico não foi alterado desde a última execução, não há razão para executar novamente os testes responsáveis ​​por testá-lo. Mais ou menos como um makefile não recompilando tudo apenas porque um arquivo foi alterado. Isso pode exigir algum trabalho, mas provavelmente também fornecerá testes mais limpos.
Leo

A metodologia de ramificação funcionará com o TFS? Escrevemos C # com TFS e a ramificação no TFS é menos amigável do que no git. Acredito que essa idéia será rejeitada, pois nunca fazemos ramificações.
Ziv

Não tenho experiência pessoal trabalhando com o TFS; no entanto, consegui encontrar este guia da Microsoft, que parece mostrar uma estratégia de ramificação semelhante à da publicação: msdn.microsoft.com/en-us/magazine/gg598921.aspx
Mike

33

A maioria dos testes de unidade deve levar menos de 10 milissegundos cada um. Tendo 'quase mil testes' é nada e deve levar talvez alguns segundos para ser executado.

Se não estiverem, pare de escrever testes de integração altamente acoplados (a menos que seja o que o código precise) e comece a escrever bons testes de unidade (começando com código bem dissociado e uso adequado de fakes / mocks / stubs / etc). Esse acoplamento afetará a qualidade do teste e o tempo necessário para escrevê-los - portanto, não se trata apenas de reduzir o tempo de execução do teste.


30
Bem, você provavelmente não deve parar de escrever testes de integração e outros testes automatizados não unitários, pois eles são úteis por si só. Você não deve confundi-los com testes de unidade e mantê-los separados, em parte porque são mais lentos.

2
Você está certo de que esses parecem ser testes de integração.
Tom Squires

9
Esta resposta não é produtiva. Em primeiro lugar, estabelece uma expectativa irracional. Existem despesas gerais na própria estrutura de teste de unidade; o fato de cada teste levar menos de um milissegundo não implica que mil testes devam levar menos de alguns segundos. O fato de todo o conjunto de testes do OP terminar em 2-3 minutos é um sinal muito bom, na maioria das medidas.
rwong

6
@rwong - desculpe, eu chamo besteira. A métrica que obtive foi a execução dos dois diferentes projetos profissionais disponíveis para mim: um com ~ 300 testes, outro com ~ 30000 e olhando para os tempos de execução dos testes. Uma suíte de testes que leva 2-3 minutos para <1000 testes é atroz e é um sinal de que os testes não são suficientemente isolados.
Telastyn

2
@rwong Na mesma linha que Telastyn, aqui estão alguns dados meus: Mesmo com alguns testes maiores do que o ideal, a estrutura de teste ( py.test) faz toneladas de mágica em segundo plano e tudo é puro código Python ("100x mais lento que C "), a execução dos cerca de 500 testes em um projeto meu leva menos de 6 segundos em um netbook lento de vários anos. Este número é aproximadamente linear no número de testes; embora exista alguma sobrecarga de inicialização, ela é amortizada em todos os testes e a sobrecarga por teste é O (1).

16

Existem várias abordagens que usei para resolver um problema semelhante:

  1. Verifique o tempo de execução, encontre todos os testes mais lentos e analise por que eles demoram tanto para serem executados .
  2. Você tem 100 projetos, pode ser que você não precise construí-los e testá-los sempre? Você poderia executar todos os mais unittest apenas em uma noite constrói? Crie várias configurações de compilação 'rápidas' para uso diário . O servidor de CI executará apenas um conjunto limitado de projetos unittests relacionados às partes "quentes" do seu processo de desenvolvimento atual .
  3. Zombe e isole tudo que puder , evite E / S de disco / rede sempre que possível
  4. Quando não é possível isolar essas operações, você pode ter testes de integração? Pode ser que você possa agendar testes de integração apenas para compilações noturnas ?
  5. Verifique todos os singletons ocasionais, que mantêm referências a instâncias / recursos e consomem memória, isso pode levar à degradação do desempenho durante a execução de todos os testes.

Além disso, você pode usar as seguintes ferramentas para facilitar sua vida e executar os testes mais rapidamente

  1. Confirmar Gated alguns servidores de IC podem ser configurados para executar a construção e o teste antes de confirmar o código no repositório de origem. Se alguém confirmar o código sem executar todos os testes anteriormente, que também contém testes com falha, ele será rejeitado e retornado ao autor.
  2. Configure o servidor de IC para executar testes em paralelo : usando várias máquinas ou processos. São exemplos pnunite configuração de IC com vários nós.
  3. Plug-in de teste contínuo para desenvolvedores, que executará automaticamente todos os testes durante a gravação do código.

12

0. Ouça seus programadores.

Se eles não estão executando os testes, significa que eles percebem que o custo (aguardando a execução dos testes, lidando com falhas falsas) é maior que o valor (detectando bugs imediatamente). Diminua os custos, aumente o valor e as pessoas farão os testes o tempo todo.

1. Faça seus testes 100% confiáveis.

Se você tiver testes que falham com falsos negativos, lide com isso imediatamente. Corrija-os, altere-os, elimine-os, o que for necessário para garantir 100% de confiabilidade. (Não há problema em ter um conjunto de testes não confiáveis, mas ainda úteis, que você pode executar separadamente, mas o corpo principal dos testes deve ser confiável.)

2. Altere seus sistemas para garantir que todos os testes passem o tempo todo.

Use sistemas de integração contínua para garantir que apenas as confirmações passantes sejam mescladas à ramificação principal / oficial / de liberação / qualquer que seja.

3. Mude sua cultura para valorizar 100% de aprovação nos testes.

Ensine a lição que uma tarefa não é "concluída" até que 100% dos testes passem e ela foi mesclada à ramificação principal / oficial / de liberação / qualquer que seja.

4. Faça os testes rapidamente.

Trabalhei em projetos em que os testes demoram um segundo e em projetos em que eles duram o dia todo. Existe uma forte correlação entre o tempo que leva para executar os testes e minha produtividade.

Quanto mais testes demoram para ser executados, menos vezes você os executa. Isso significa que você ficará mais tempo sem receber feedback sobre as alterações que está fazendo. Isso também significa que você passará mais tempo entre as confirmações. Confirmar com mais frequência significa etapas menores que são mais fáceis de mesclar; confirmar o histórico é mais fácil de seguir; encontrar um bug na história é mais fácil; retroceder também é mais fácil.

Imagine testes que executam tão rápido que você não se importa de executá-los automaticamente toda vez que compila.

Fazer testes rapidamente pode ser difícil (foi o que o OP pediu, certo!). Desacoplamento é a chave. Zombarias / falsificações estão OK, mas acho que você pode fazer melhor refatorando para tornar desnecessárias as zombarias / falsificações. Veja o blog de Arlo Belshee, começando com http://arlobelshee.com/post/the-no-mocks-book .

5. Faça testes úteis.

Se os testes não falham quando você estraga tudo, então qual é o objetivo? Ensine-se a escrever testes que detectem os erros que você provavelmente criará. Esta é uma habilidade em si mesma e exigirá muita atenção.


2
Concordo totalmente, especialmente os pontos 3 e 1. Se os desenvolvedores não estiverem executando testes, os testes serão interrompidos, o ambiente será interrompido ou ambos. O ponto 1 é o mínimo. Falhas falsas são piores do que a falta de testes. Porque as pessoas aprendem a aceitar falhas. Uma vez tolerada a falha, ela se espalha e é preciso um grande esforço para voltar a 100% de aprovação e ESPERAR 100% de aprovação. Comece a corrigir isso hoje .
Bill IV

E como você pode não concordar com o # 5?!? além de 1 e 3, ou Parreira, 2 e 4 também! Enfim, ótima resposta ao redor.
Fourpastmidnight 22/08/18

4

Alguns minutos são bons para testes de unidade. No entanto, lembre-se de que existem três tipos principais de testes:

  1. Testes de unidade - teste cada "unidade" (classe ou método) independentemente do restante do projeto
  2. Testes de integração - teste o projeto como um todo, geralmente fazendo chamadas para o programa. Alguns projetos que eu já vi combinam isso com testes de regressão. Há significativamente menos zombaria aqui do que testes de unidade
  3. Testes de regressão - teste o projeto concluído como um todo, pois o conjunto de testes é um usuário final. Se você tiver um aplicativo de console, use o console para executar e testar o programa. Você nunca expõe os internos a esses testes e qualquer usuário final do seu programa deve (em teoria) ser capaz de executar seu conjunto de testes de regressão (mesmo que nunca o faça)

Estes estão listados em ordem de velocidade. Os testes de unidade devem ser rápidos. Eles não detectam todos os erros, mas estabelecem que o programa é decentemente são. Os testes de unidade devem ser executados em 3 minutos ou menos ou com um hardware decente. Você diz que só tem 1000 testes de unidade e eles levam de 2 a 3 minutos? Bem, provavelmente está tudo bem.

Coisas a verificar:

  • Certifique-se de garantir que seus testes de unidade e testes de integração sejam separados. Os testes de integração sempre serão mais lentos.

  • Verifique se os testes de unidade estão sendo executados em paralelo. Não há motivo para não fazerem se são verdadeiros testes de unidade

  • Verifique se seus testes de unidade são "livres de dependência". Eles nunca devem acessar um banco de dados ou o sistema de arquivos

Fora isso, seus testes não parecem muito ruins agora. No entanto, para referência, um dos meus amigos de uma equipe da Microsoft tem 4.000 testes de unidade que são executados em menos de 2 minutos em hardware decente (e é um projeto complicado). É possível fazer testes de unidade rápidos. Eliminar dependências (e simular apenas o necessário) é a principal coisa para obter velocidade.


3

Treine seus desenvolvedores no Personal Software Process (PSP), ajudando-os a entender e melhorar seu desempenho usando mais disciplina. Escrever código não tem nada a ver com bater os dedos em um teclado e depois pressionar um botão de compilação e check-in.

O PSP costumava ser muito popular no passado quando a compilação de código era um processo que demorava muito tempo (horas / dias em um mainframe para que todos tivessem que compartilhar o compilador). Mas quando as estações de trabalho pessoais se tornaram mais poderosas, todos nós aceitamos o processo:

  1. digite algum código sem pensar
  2. clique em compilar / compilar
  3. corrija sua sintaxe para compilá-la
  4. execute testes para ver se o que você escreveu realmente faz sentido

Se você pensa antes de digitar e, depois de digitar, revise o que escreveu, poderá reduzir o número de erros antes de executar um conjunto de compilação e teste. Aprenda a não pressionar a compilação 50 vezes por dia, mas talvez uma ou duas vezes, então é menos importante que o tempo de compilação e teste demore mais alguns minutos.


2
Concordo plenamente com sua lista, mas absolutamente não com "executar a compilação apenas duas vezes por dia é melhor que 50 vezes".
Doc Brown

3

Uma maneira possível: divida sua solução. Se uma solução possui 100 projetos, é bastante incontrolável. Só porque dois projetos (digamos A e B) usam algum código comum de outro projeto (digamos Lib) não significa que eles precisam estar na mesma solução.

Em vez disso, você pode criar a solução A com os projetos A e Lib e também a solução B com os projetos B e Lib.


2

Estou em uma situação similar. Eu tenho testes de unidade que testam a comunicação com o servidor. Eles estão testando o comportamento com tempos limite, cancelando conexões etc. Todo o conjunto de testes dura 7 minutos.

7 minutos é um período relativamente curto, mas não é algo que você fará antes de cada confirmação.

Também temos um conjunto de testes de interface do usuário automatizados, cujo tempo de execução é de 2 horas. Não é algo que você deseja executar todos os dias no seu computador.

Então o que fazer?

  1. Alterar os testes geralmente não é muito eficaz.
  2. Execute apenas os testes relevantes antes da sua confirmação.
  3. Execute todos os seus testes todos os dias (ou várias vezes ao dia) em um servidor de compilação. Isso também lhe dará a possibilidade de gerar bons relatórios de cobertura e análise de código.

O importante é: todos os seus testes devem ser executados com frequência, porque é importante encontrar os bugs. No entanto, não é absolutamente necessário encontrá-los antes do commit.


11
Quanto aos testes que falam com servidores: se estiver falando com um servidor, não é realmente um teste de unidade, é algo mais alto. Se eu fosse você, separaria os testes de unidade (que devem ser rápidos) e pelo menos os executaria antes de cada confirmação. Dessa forma, você pelo menos obterá as coisas rápidas (coisas que não precisam conversar com o servidor) antes que o código seja confirmado.
Michael Kohne

@MichaelKohne Eu sabia que alguém iria descobrir. Eu sei que eles não são exatamente testes de unidade, mas servem para o mesmo propósito, é apenas sobre como você os nomeia.
Sulthan

11
principalmente é sobre como você os nomeia, mas é bom ter em mente a diferença (seja qual for o nome que você usar). Se você não diferencia, então (na minha experiência) os desenvolvedores tendem a escrever apenas testes de nível superior. Nesse ponto, você não realiza os testes, forçando-o a ser sensível em suas abstrações e acoplamentos.
Michael Kohne

1

Embora sua descrição do problema não forneça uma visão completa da base de código, acho que posso dizer com segurança que seu problema é duplo.

Aprenda a escrever os testes certos.

Você diz que tem quase mil testes e tem 120 projetos. Supondo que no máximo metade desses projetos sejam projetos de teste, você tem 1000 testes para 60 projetos de código de produção. Isso fornece cerca de 16 a 17 testes pr. projeto!!!

Essa é provavelmente a quantidade de testes que eu teria que cobrir cerca de uma a duas classes em um sistema de produção. Portanto, a menos que você tenha apenas 1-2 classes em cada projeto (nesse caso, a estrutura do seu projeto é muito refinada), seus testes são muito grandes, eles cobrem muito terreno. Você diz que este é o primeiro projeto que você está executando o TDD corretamente. Por exemplo, os números que você apresenta indicam que esse não é o caso, você não está fazendo a propriedade TDD.

Você precisa aprender a escrever os testes certos, o que provavelmente significa que você precisa aprender a tornar o código testável em primeiro lugar. Se você não encontrar a experiência dentro da equipe para fazer isso, sugiro contratar ajuda externa, por exemplo, na forma de um ou dois consultores ajudando sua equipe por um período de 2 a 3 meses para aprender a escrever código testável e pequenos testes unitários mínimos.

Como comparação, no projeto .NET em que estou trabalhando atualmente, podemos executar aproximadamente 500 testes de unidade em menos de 10 segundos (e isso nem foi medido em uma máquina de alta especificação). Se esses fossem seus números, você não teria medo de executá-los localmente de vez em quando.

Aprenda a gerenciar a estrutura do projeto.

Você dividiu a solução em 120 projetos. Pelos meus padrões, é uma quantidade impressionante de projetos.

Portanto, se faz sentido ter realmente essa quantidade de projetos (o que acho que não tem - mas sua pergunta não fornece informações suficientes para fazer um julgamento qualificado disso), você precisa dividir os projetos em componentes menores que pode ser compilado, com versão e implantado separadamente. Portanto, quando um desenvolvedor executa a unidade do conjunto de testes, ele só precisa executar os testes relacionados ao componente em que está trabalhando atualmente. O servidor de compilação deve verificar se tudo se integra corretamente.

Mas dividir um projeto em múltiplos componentes, compilado, com versão e implantado separadamente exige, em minha experiência, uma equipe de desenvolvimento muito madura, uma equipe mais madura do que eu sinto que sua equipe é.

Mas, de qualquer forma, você precisa fazer algo sobre a estrutura do projeto. Divida os projetos em componentes separados ou comece a mesclar projetos.

Pergunte a si mesmo se você realmente precisa de 120 projetos?

ps Você pode querer conferir o NCrunch. É um plug-in do Visual Studio que executa seu teste automaticamente em segundo plano.


0

O teste JUnit normalmente deve ser rápido, mas alguns deles precisam levar algum tempo para serem executados.

Por exemplo, o teste do banco de dados geralmente leva alguns minutos para inicializar e concluir.

Se você tiver centenas de testes, mesmo que sejam rápidos, eles exigirão muito tempo para serem executados devido ao seu número.

O que pode ser feito é:

1) Identifique os testes cruciais. Aqueles para as partes mais importantes das bibliotecas e aqueles com maior probabilidade de falhar após as alterações. Somente esses testes devem ser executados sempre na compilação. Se algum código for quebrado com frequência, seus testes deverão ser obrigatórios, mesmo que demorem muito para serem executados, por outro lado, se alguma parte do software nunca causou um problema, você pode pular com segurança os testes em cada build.

2) Prepare o servidor de integração contínua, que executará todos os testes em segundo plano. Depende de você se você decidir construir a cada hora ou após cada confirmação (o segundo só fará sentido se você quiser detectar automaticamente cuja confirmação causou problemas).


0

Problemas que eu já vi:

a) Usando o COI para criar elementos de teste. 70 segundos -> 7 segundos removendo o Container.

b) Não zombando de todas as classes. Mantenha seus testes de unidade em um único elemento. Eu já vi testes que divagam através de algumas aulas. Estes não são testes de unidade e são muito mais propensos a quebrar.

c) Crie um perfil para descobrir o que estava acontecendo. Eu achei que o construtor estava construindo coisas que eu não precisava, então localizei e reduzi os tempos de execução.

d) perfil. talvez o código não seja tão bom e você possa obter alguma eficiência com uma revisão.

e) Remova as dependências. Manter o teste executável pequeno reduzirá o tempo de carregamento. Use uma biblioteca de interfaces e contêineres IOC para executar sua solução final, mas seus principais projetos de teste devem ter apenas a biblioteca de interfaces definida. Isso garante a separação, facilita o teste e também diminui a impressão do pé de teste.


0

Sinto sua dor e já corri em vários lugares onde a velocidade de construção pode ser bastante melhorada. No entanto, o número de coisas que recomendo é medir em detalhes granulares para descobrir onde a sua construção está demorando mais. Por exemplo, eu tenho uma compilação com cerca de 30 projetos que levam pouco mais de um minuto para serem executados. No entanto, isso é apenas parte da imagem. Também sei quais projetos demoram mais para serem construídos, o que ajuda a concentrar meus esforços.

Coisas que consomem tempo de construção:

  • Download de pacotes (Nuget para C #, Maven para Java, Gem para Ruby, etc.)
  • Copiando grandes quantidades de arquivos no sistema de arquivos (exemplo: arquivos de suporte GDAL)
  • Abrir conexões com o banco de dados (alguns levam mais de um segundo por conexão para negociar)
  • Código baseado em reflexão
  • Código gerado automaticamente
  • Usando exceções para controlar o fluxo do programa

As bibliotecas simuladas usam reflexão ou injetam código usando as bibliotecas bytecode para gerar a simulação para você. Embora seja muito conveniente, consome tempo de teste. Se você estiver gerando zombarias dentro de um loop em seu teste, isso poderá adicionar uma quantidade mensurável de tempo aos testes de unidade.

Existem maneiras de corrigir os problemas:

  • Mova testes envolvendo um banco de dados para integração (ou seja, apenas no servidor de criação de IC)
  • Evite criar zombarias em loops em seus testes. De fato, evite os loops dos seus testes completamente. Provavelmente, você pode obter os mesmos resultados usando um teste parametrizado nesse caso.
  • Considere dividir sua solução massiva em soluções separadas

Quando sua solução contém mais de 100 projetos, você tem uma combinação de código de biblioteca, testes e código de aplicativo. Cada uma das bibliotecas pode ser sua própria solução com seus testes associados. O Jet Brains Team City é um servidor de criação de CI que funciona como um servidor Nuget - e tenho certeza de que não é o único. Isso oferece a flexibilidade de mover as bibliotecas que provavelmente não são alteradas frequentemente para suas próprias soluções / projetos e usar o Nuget para resolver as dependências do código do aplicativo. Soluções menores significam que você pode fazer alterações em uma biblioteca rapidamente e sem problemas e aproveitar os benefícios da solução principal.


-1

Seu ambiente de teste pode ser executado em qualquer lugar? Se possível, use a computação em nuvem para executar os testes. Divida os testes entre N máquinas virtuais. Se o tempo para executar os testes em uma única máquina for T1 segundos, o tempo para executá-los, T2, poderá se aproximar de T2 = T1 / N. (Supondo que cada caso de teste leve aproximadamente a mesma quantidade de tempo.) E você só precisará pagar pelas VMs quando as estiver usando. Portanto, você não tem um monte de máquinas de teste em algum laboratório em algum lugar 24/7. (Adoraria poder fazer isso onde trabalho, mas estamos vinculados a hardware específico. Não há VMs para mim.)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.