É uma boa prática executar testes de unidade em ganchos de controle de versão?

40

Do ponto de vista técnico, é possível adicionar alguns ganchos de pré / pós que executarão testes de unidade antes de permitir que alguma confirmação específica seja mesclada à ramificação padrão remota.

Minha pergunta é - é melhor manter os testes de unidade no pipeline de construção (assim, introduzindo commits quebrados no repo) ou é melhor não permitir que commits "ruins" aconteçam.

Eu percebo que não estou limitado com essas duas opções. Por exemplo, posso permitir que todos os commits sejam ramificados e testados antes de empurrar o merge commit para o repo. Mas se você tiver que escolher entre exatamente essas duas soluções, qual delas você escolherá e por quais razões exatamente?

    
por shabunc 24.10.2014 / 00:39
fonte

9 respostas

33

Não, não é por dois motivos:

Velocidade

As confirmações devem ser rápidas. Um commit que leva 500 ms, por exemplo, é muito lento e encorajará os desenvolvedores a se comprometerem com mais parcimônia. Dado que em qualquer projeto maior do que um Hello World, você terá dezenas ou centenas de testes, levará muito tempo para executá-los durante o pré-commit.

É claro que as coisas pioram para projetos maiores com milhares de testes que são executados por minutos em uma arquitetura distribuída, ou semanas ou meses em uma única máquina.

A pior parte é que não há muito que você possa fazer para torná-lo mais rápido. Projetos de Python pequenos que têm, digamos, cem testes de unidade, levam pelo menos um segundo para serem executados em um servidor comum, mas geralmente são muito mais longos. Para um aplicativo C #, a média será de quatro a cinco segundos, devido ao tempo de compilação.

A partir desse ponto, você pode pagar mais US $ 10.000 por um servidor melhor, o que reduzirá o tempo, mas não muito, ou executará testes em vários servidores, o que só retardará as coisas.

Ambos pagam bem quando você tem milhares de testes (assim como testes funcionais, de sistema e de integração), permitindo executá-los em questão de minutos em vez de semanas, mas isso não ajudará em projetos de pequena escala. / p>

O que você pode fazer é:

  • Incentive os desenvolvedores a executar testes strongmente relacionados ao código que eles modificaram localmente antes de fazer um commit. Eles possivelmente não podem executar milhares de testes unitários, mas podem executar cinco ou dez deles.

    Certifique-se de que encontrar testes relevantes e executá-los é realmente fácil (e rápido). O Visual Studio, por exemplo, é capaz de detectar quais testes podem ser afetados pelas alterações feitas desde a última execução. Outros IDEs / plataformas / idiomas / frameworks podem ter funcionalidade semelhante.

  • Mantenha o commit o mais rápido possível. Reforçar as regras de estilo é bom, porque muitas vezes, é o único lugar para fazê-lo e porque essas verificações geralmente são incrivelmente rápidas. Fazer análise estática está OK assim que ela se mantém rápida, o que raramente é o caso. A execução de testes unitários não está correta.

  • Execute testes de unidade no seu servidor de integração contínua.

  • Certifique-se de que os desenvolvedores sejam informados automaticamente quando eles quebraram a compilação (ou quando os testes de unidade falharam, o que é praticamente a mesma coisa se você considerar um compilador como uma ferramenta que verifica alguns dos possíveis erros que você pode introduzir em seu código).

    Por exemplo, ir a uma página da web para verificar as últimas compilações não é uma solução. Eles devem ser informados automaticamente . Mostrar um pop-up ou enviar um SMS são dois exemplos de como eles podem ser informados.

  • Certifique-se de que os desenvolvedores entendam que interromper a compilação (ou testes de regressão com falha) não é correto e que, assim que isso acontece, a prioridade é corrigi-lo. Não importa se eles estão trabalhando em um recurso de alta prioridade que seu chefe pediu para enviar para amanhã: eles falharam na construção, eles deveriam consertá-lo.

Segurança

O servidor que hospeda o repositório não deve executar código personalizado, como testes de unidade, especialmente por motivos de segurança. Essas razões já foram explicadas em corredor de CI no mesmo servidor do GitLab?

Se, por outro lado, sua idéia é lançar um processo no servidor de compilação a partir do gancho pre-commit, então ele irá retardar ainda mais os commits.

    
por 24.10.2014 / 02:05
fonte
39

Deixe-me ser o único a discordar dos meus colegas respondentes.

Isso é conhecido como Gated Check-ins no mundo do TFS, e espero em outro lugar. Quando você tenta fazer o check-in em uma ramificação com o check-in fechado, o shelveset é enviado para o servidor, o que garante que suas alterações sejam criadas e os testes de unidade especificados (lidos: todos) sejam aprovados. Se não, avisa que você é um macaco mau que quebrou a construção. Se o fizerem, as alterações entrarão no controle de origem (yay!).

Na minha experiência, check-ins fechados são um dos processos mais importantes para testes de unidade bem-sucedidos - e, por extensão, a qualidade do software.

Por quê?

  • Como os check-ins fechados obrigam as pessoas a corrigir os testes interrompidos. Assim que os testes quebrados se tornam algo que as pessoas podem fazem, em vez de devem fazer, eles se tornam desprivilegiados por engenheiros preguiçosos e / ou pessoas de negócios agressivas.
    • Quanto mais tempo um teste é quebrado, mais difícil (e mais caro) é corrigir.
  • Porque, assim que as pessoas devem executar os testes, em vez de precisarem executar os testes, a execução dos testes é contornada por engenheiros preguiçosos / esquecidos e / ou pessoas de negócios agressivas.
  • Porque, assim que os testes de unidade afetam seu tempo de confirmação, as pessoas realmente começam a se preocupar em fazer testes em unidade . A velocidade é importante. Questões de reprodutibilidade. A confiabilidade é importante. Questões de isolamento.

E, claro, há o benefício que você criou originalmente - quando você bloqueou check-ins e um conjunto sólido de testes, cada conjunto de alterações é "estável". Você salva toda aquela sobrecarga (e potencial de erro) de "quando foi a última boa construção?" - todas as construções são boas o suficiente para serem desenvolvidas.

Sim, leva tempo para criar e executar os testes. Na minha experiência 5-10 minutos para um bom tamanho C # app e testes de unidade ~ 5k. E eu não me importo com isso. Sim, as pessoas devem fazer check-in com frequência. Mas eles também devem atualizar suas tarefas com frequência ou checar seus e-mails, ou tomar um café ou dezenas de outras coisas "que não funcionam no código" que compõem o trabalho de um engenheiro de software para ocupar esse tempo. Verificar o código incorreto é muito mais caro do que 5 a 10 minutos.

    
por 24.10.2014 / 21:19
fonte
39

As confirmações devem ser executadas rapidamente. Quando eu comprometo algum código, eu quero que ele seja empurrado para o servidor. Eu não quero esperar alguns minutos enquanto ele executa uma bateria de testes. Eu sou responsável pelo que eu envio para o servidor e não preciso de ninguém me babando com ganchos de commit.

Dito isto, uma vez que chegue ao servidor, ele deve ser analisado, testado e construído imediatamente (ou dentro de um curto espaço de tempo). Isso me alertaria para o fato de que os testes de unidade estão quebrados, ou não foram compilados, ou eu fiz uma bagunça mostrada pelas ferramentas de análise estática disponíveis. Quanto mais rápido isso é feito (a construção e análise), mais rápido o meu feedback e mais rápido eu sou capaz de consertá-lo (os pensamentos não foram completamente trocados do meu cérebro).

Portanto, não, não coloque testes e tais em ganchos de commit no cliente. Se você precisar, coloque-os no servidor em um post commit (porque você não tem um servidor de IC) ou no servidor de build do CI e me alerte apropriadamente para problemas com o código. Mas não bloqueie o commit de acontecer em primeiro lugar.

Eu também devo salientar que com algumas interpretações do Desenvolvimento Orientado a Testes, um deve verificar em um teste de unidade que quebra primeiro . Isso demonstra e documenta que o bug está presente. Em seguida, um check-in posterior seria o código que corrige o teste de unidade. Evitar qualquer check-in até que os testes de unidade passem reduziria o valor efetivo da verificação em um teste de unidade que não documenta o problema.

Relacionados: Devo ter testes de unidade para defeitos conhecidos? e Qual é o valor da verificação em testes de unidade com falha?

    
por 24.10.2014 / 02:11
fonte
10

Em princípio, acho que faz sentido evitar que as pessoas façam alterações na linha principal que quebram a construção. Ou seja, o processo para fazer alterações na ramificação principal do seu repositório deve exigir a garantia de que todos os testes ainda passem. Quebrar a construção é simplesmente muito dispendioso em termos de tempo perdido para que todos os engenheiros do projeto façam qualquer outra coisa.

No entanto, a solução específica de commit hooks não é um bom plano.

  1. O desenvolvedor precisa esperar que os testes sejam executados durante o commit. Se o desenvolvedor tiver que esperar em sua estação de trabalho para que todos os testes sejam aprovados, você desperdiçou um tempo valioso de engenheiro. O engenheiro precisa ser capaz de passar para a próxima tarefa, mesmo que tenha que voltar porque os testes acabaram falhando.
  2. Os desenvolvedores podem querer cometer código quebrado em uma ramificação. Em uma tarefa maior, a versão de desenvolvedores do código pode gastar muito tempo sem passar por um estado. Obviamente, mesclar esse código na linha principal seria muito ruim. Mas é importante que o desenvolvedor ainda possa usar o controle de versão para acompanhar seu progresso.
  3. Existem boas razões ocasionais para ignorar o processo e ignorar os testes.
por 24.10.2014 / 05:07
fonte
3

Não, você não deveria, como outras respostas apontaram.

Se você deseja ter uma base de código garantida para não ter testes com falha, você pode desenvolver ramificações de recursos e fazer pedidos para o mestre. Em seguida, você pode definir condições prévias para aceitar essas solicitações de recebimento. O benefício é que você pode acelerar bastante e os testes são executados em segundo plano.

    
por 24.10.2014 / 08:10
fonte
2

Ter que esperar por uma construção e testes bem-sucedidos em cada compromisso com o ramo principal é realmente horrível, acho que todos concordam com isso.

Mas há outras maneiras de alcançar um ramo principal consistente. Aqui está uma sugestão, um pouco similar na veia de check-ins bloqueados no TFS, mas que é generalizável para qualquer sistema de controle de versão com ramificações, embora eu use principalmente termos de git:

  • Possua um ramo temporário para o qual você só pode consolidar mesclagens entre seus ramos dev e o ramo principal

  • Configure um gancho que inicie ou enfileire uma compilação e teste em confirmações feitas na ramificação de preparação, mas isso não faz com que o committer espere

  • Na criação e nos testes bem-sucedidos, faça automaticamente a ramificação principal encaminhar se estiver atualizada

    Nota: não automaticamente mescla na ramificação principal, porque a mesclagem testada, se não uma mesclagem direta do ponto de vista da ramificação principal, pode falhar quando mesclada na ramificação principal com confirmações entre

Como consequência:

  • Proibir que o ser humano se comprometa com o ramo principal, automaticamente, se puder, mas também como parte do processo oficial, se houver uma brecha ou se não for tecnicamente viável impor isso

    Pelo menos, você pode ter certeza de que ninguém fará isso de forma não intencional, ou sem malícia, já que é uma regra básica. Você não deve tentar fazer isso nunca.

  • Você terá que escolher entre:

    • Uma única ramificação temporária, que fará fusões bem-sucedidas, na verdade, falhará se uma mesclagem anterior, ainda que não compilada e não testada, falhar

      Pelo menos, você saberá qual mesclagem falhou e fará com que alguém o corrija, mas as mesclagens entre elas não serão rastreáveis trivialmente (pelo sistema de controle de versão) para os resultados de outras compilações e testes.

      Você pode analisar a anotação de arquivo (ou culpar), mas às vezes uma alteração em um arquivo (por exemplo, configuração) gerará erros em locais inesperados. No entanto, este é um evento bastante raro.

    • Múltiplas ramificações de migração de dados, que permitirão que mesclagens bem-sucedidas e não conflitantes cheguem à ramificação principal

      Mesmo no caso de alguma outra ramificação temporária ter fusões com falha sem conflito . A rastreabilidade é um pouco melhor, pelo menos no caso de uma fusão não esperar uma mudança afetada de outra fusão. Mas, novamente, isso é raro o suficiente para não se preocupar com todos os dias ou todas as semanas.

      Para ter mesclagens não conflitantes na maioria das vezes, é importante dividir sensivelmente as ramificações de preparação, por exemplo, por equipe, por camada ou por componente / projeto / sistema / solução (o que você nomeia).

      Se o ramo principal foi encaminhado para outra mesclagem, você precisará mesclar novamente. Espero que isso não seja um problema com fusões não conflitantes ou com pouquíssimos conflitos.

Em comparação com check-ins fechados, a vantagem aqui é que você tem a garantia de uma ramificação principal em funcionamento, porque a ramificação principal só tem permissão para avançar, não para mesclar automaticamente as alterações com o que foi confirmado. Então, o terceiro ponto é a diferença essencial.

    
por 25.10.2014 / 17:51
fonte
2

Eu prefiro "passar nos testes de unidade" para ser um portão para enviar o código. No entanto, para fazer esse trabalho, você precisará de algumas coisas.

Você precisará de uma estrutura de construção que armazene artefatos em cache.

Você precisará de uma estrutura de teste que armazene em cache o status de teste de execuções de teste (com êxito), com qualquer artefato (s) especificado (s).

Dessa forma, os check-ins com testes de unidade de passagem serão rápidos (verificação cruzada da fonte para o artefato que foi construído quando o desenvolvedor verificou os testes antes do check-in), aqueles com testes de unidade com falha serão bloqueados e os desenvolvedores que convenientemente se esquecem de verificar as compilações antes de confirmar serão incentivados a lembrar de fazer isso da próxima vez, porque o ciclo de compilação e teste é demorado.

    
por 27.10.2015 / 14:03
fonte
1

Eu diria que depende do projeto e do escopo dos testes automatizados que são executados no "commit".

Se os testes que você deseja executar no gatilho de check-in forem realmente rápidos ou se o fluxo de trabalho do desenvolvedor forçar algum trabalho administrativo após esse check-in de qualquer maneira, acho que isso não deve importar muito e forçar os desenvolvedores para verificar apenas coisas que absolutamente executam os testes mais básicos. (Eu estou supondo que você só iria executar os testes mais básicos em tal gatilho.)

E eu acho que, com a velocidade / fluxo de trabalho permitindo, é uma boa coisa não empurrar alterações para outros desenvolvedores que falharem nos testes - e você só sabe se eles falharem se você executá-los.

Você escreve "commit ... to remote branch" na questão, o que implicaria para mim que isto é (a) não algo que um desenvolvedor faz a cada poucos minutos, então uma pequena espera pode ser muito bem aceita , e (b) que depois de um commit tal, as alterações no código podem impactar outros devs, para que cheques adicionais possam estar em ordem.

Eu posso concordar com as outras respostas sobre "não faça seus desenvolvedores passarem polegares enquanto esperam" por tal operação.

    
por 24.10.2014 / 21:13
fonte
1

Confirmações interrompidas não devem ser permitidas no tronco , porque o tronco é o que pode entrar em produção. Então você precisa garantir que existe um gateway que precisa passar antes de entrar no trunk . No entanto, os commits quebrados podem ser totalmente bons em um repo, desde que ele não esteja no trunk.

Por outro lado, exigir que os desenvolvedores esperem / corrijam os problemas antes de enviar as alterações para o repositório tem vários inconvenientes.

Alguns exemplos:

  • No TDD, é comum confirmar e enviar testes com falha para novos recursos antes de começar a implementar o recurso
  • O mesmo vale para reportar bugs comprometendo e realizando um teste com falha
  • O envio de código incompleto permite que 2 ou mais pessoas trabalhem em um recurso em paralelo com facilidade
  • Sua infra-estrutura de IC pode demorar na verificação, mas o desenvolvedor não precisa esperar
por 26.07.2018 / 12:31
fonte