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


43

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

Minha pergunta é: é melhor manter os testes de unidade no pipeline de construção (introduzindo confirmações quebradas para repo) ou é melhor não permitir que as confirmações "ruins" ocorram.

Percebo que não estou limitado a essas duas opções. Por exemplo, posso permitir que todas as confirmações sejam ramificadas e testadas antes de enviar a confirmação de mesclagem para repo. Mas se você tiver que escolher exatamente entre essas duas soluções, qual delas escolherá e por quais motivos exatamente?


Respostas:


35

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

Rapidez

As confirmações devem ser rápidas. Uma confirmação que leva 500 ms., Por exemplo, é muito lenta e incentiva os desenvolvedores a se comprometerem com mais moderação. Como em qualquer projeto maior que um Hello World, você terá dezenas ou centenas de testes, levará muito tempo para executá-los durante a pré-confirmação.

Obviamente, 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. Pequenos projetos Python que possuem, digamos, cem testes de unidade, levam pelo menos um segundo para serem executados em um servidor médio, mas geralmente muito mais tempo. 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 US $ 10.000 extras por um servidor melhor, o que reduzirá o tempo, mas não muito, ou executará testes em vários servidores, o que só tornará as coisas mais lentas.

Ambos pagam bem quando você tem milhares de testes (bem 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.

Em vez disso, o que você pode fazer é:

  • Incentive os desenvolvedores a executar testes fortemente relacionados ao código modificado localmente antes de realizar uma confirmação. Eles possivelmente não podem executar milhares de testes de unidade, mas podem executar cinco a dez deles.

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

  • Mantenha o commit o mais rápido possível. A aplicação de regras de estilo é aceitável, porque geralmente é o único lugar para fazê-lo e porque essas verificações são incrivelmente rápidas. Fazer análises estáticas é bom assim que fica rápido, o que raramente é o caso. A execução de testes de unidade não está OK.

  • Execute testes de unidade no 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 no seu código).

    Por exemplo, ir a uma página da web para verificar as últimas versõ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 não é bom interromper a compilação (ou falhar nos testes de regressão) e que, assim que isso acontecer, a principal prioridade será corrigi-la. Não importa se eles estão trabalhando em um recurso de alta prioridade que seu chefe pediu para enviar amanhã: eles falharam na construção, eles deveriam corrigi-la.

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. Esses motivos já foram explicados no CI runner no mesmo servidor do GitLab?

Se, por outro lado, sua idéia for iniciar um processo no servidor de compilação a partir do gancho de pré-confirmação, ele diminuirá ainda mais a confirmação.


4
Concordo, é para isso que serve um servidor de compilação. Seu controle de origem é para gerenciar o código-fonte, não garantindo que seu aplicativo funcione.
Matthew

4
Em casos extremos, a retaliação pode ser uma ferramenta de notificação necessária ao interromper a construção.

37
Meio segundo é muito lento? Essa é uma gota no balde, em comparação a dar uma olhada final no que está sendo confirmado e depois pensar e digitar um comentário de confirmação apropriado.
Doval

5
@Doval: a diferença é que, quando você dá uma olhada final ou pensa no comentário, é proativo e, portanto, não está esperando. Não é o tempo que você gasta antes de digitar o último caractere no seu IDE e o momento em que você pode começar a digitar novamente depois que a confirmação é concluída, o que importa, mas quanto você espera. É por isso que os compiladores devem ser rápidos; não importa que você gaste muito mais tempo lendo e escrevendo código, porque quando você faz isso, não está esperando, enquanto que quando o código está compilando, você fica tentado a mudar para uma atividade diferente.
Arseni Mourzenko

10
@ Thomas: não se trata de distração, mas de aborrecimento. Da mesma maneira, 100 ms. "tem um impacto mensurável" em como as pessoas estão usando um site. Mesmo padrão aqui: 100 ms. não é nada comparado ao tempo que você passa assistindo a um vídeo do YouTube ou iniciando o PC: conscientemente , você não notará a diferença entre 600 ms. e 700 ms. demora. Mas, inconscientemente, isso influencia o seu comportamento. Da mesma forma, confirmações um pouco mais lentas desencorajam você a cometer precocemente e com frequência.
Arseni Mourzenko

41

Deixe-me discordar de meus colegas respondentes.

Isso é conhecido como check-in fechado no mundo do TFS e espero em outro lugar. Quando você tenta fazer check-in em uma filial com o check-in fechado, o shelveset é enviado ao servidor, o que garante que suas alterações sejam construídas e que os testes de unidade especificados (leia-se: todos) sejam aprovados. Se não, notifica que você é um macaco ruim que quebrou a compilação. Se o fizerem, as alterações entrarão no controle de origem (yay!).

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

Por quê?

  • Porque check-ins fechados forçam as pessoas a corrigir testes quebrados. Assim que os testes quebrados se tornam algo que as pessoas podem fazer, e não devem fazer, eles são priorizados por engenheiros preguiçosos e / ou pessoas de negócios insistentes.
    • 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 deve executar os testes, executando os testes sejam contornadas por engenheiros preguiçosos / esquecido e / ou pessoas de negócios agressivo.
  • Como assim que os testes de unidade afetam seu tempo de confirmação, as pessoas realmente começam a se preocupar em fazer seus testes de unidade . A velocidade é importante. A reprodutibilidade é importante. Confiabilidade é importante. O isolamento é importante.

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

Sim, leva tempo para criar e executar os testes. Na minha experiência, de 5 a 10 minutos para um aplicativo C # de bom tamanho e ~ 5k testes de unidade. E eu não ligo para isso. Sim, as pessoas devem fazer check-in com frequência. Mas eles também devem atualizar suas tarefas com frequência, verificar seu e-mail 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.


3
Quero acrescentar que muitos projetos de código aberto têm uma distinção clara entre contribuição e comprometimento. As razões para isso são muito semelhantes ao motivo pelo qual existem check-ins fechados.
JensG

Um problema significativo com o check-in fechado é que inibe a resolução colaborativa de problemas de código difícil. Isso é ainda mais significativo com equipes distribuídas.
precisa saber é o seguinte

2
@CuriousRabbit - como você imagina? As pessoas raramente se comprometem colaborativamente, mesmo que trabalhem colaborativamente. Caso contrário, prateleiras ou ramos de tarefas sem data funcionam bem para isso, sem prejudicar o restante da equipe.
Telastyn

@ Telastyn - Shelvesets é um novo termo para mim. Entendo que esta é uma opção do MS VS, que não é uma opção para um grande número de projetos. No campo do desenvolvimento de aplicativos para dispositivos móveis, o VS é um não participante.
precisa saber é o seguinte

3
@CuriousRabbit - realmente? Uma equipe distribuída por 17 horas se preocupa mais em esperar 10 minutos por uma confirmação do que na possibilidade de uma compilação interrompida enquanto a parte infratora está dormindo? Isso parece ... Menos que o ideal.
Telastyn

40

As confirmações devem ser rápidas. Quando comprometo algum código, quero que ele seja enviado ao servidor. Não quero esperar alguns minutos enquanto executa uma bateria de testes. Sou responsável pelo que envio ao servidor e não preciso de ninguém para cuidar de mim com ganchos de confirmação.

Dito isto, quando chegar ao servidor, ele deverá ser analisado, testado em unidade 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 fiz uma bagunça mostrada pelas ferramentas de análise estática disponíveis. Quanto mais rápido isso é feito (construção e análise), mais rápido meu feedback e mais rápido eu sou capaz de corrigi-lo (os pensamentos não foram completamente trocados do meu cérebro).

Portanto, não, não coloque testes e ganchos de confirmação no cliente. Se necessário, coloque-os no servidor em uma confirmação de postagem (porque você não possui um servidor de IC) ou no servidor de criação de IC e me avise adequadamente sobre problemas com o código. Mas não impeça que o commit aconteça em primeiro lugar.

Devo também salientar que, com algumas interpretações do Test Driven Development, deve-se verificar um teste de unidade que interrompa 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. Impedir qualquer verificação 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.

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


2
Commits should run fast.quais são os benefícios disso? Estou curioso, porque atualmente usamos check-in fechado. Normalmente, meus check-ins são uma acumulação de mais ou menos uma hora de trabalho, portanto uma espera de 5 minutos não é grande coisa. Na verdade eu descobri que seus normalmente as vezes quando eu estou em uma corrida que a compilação de validação é mais útil para a captura de erros bobos (como resultado da pressa)
Justin

1
@ Justin Uma espera de cinco minutos é uma espera de cinco minutos, não importa onde esteja. Um não deve precisar sair espadas nerf cada vez que você a cometer. E não é incomum eu dividir uma hora de trabalho em várias confirmações que são unidades conceituais uma da outra - "confirme o código de serviço restante" seguido de "confirme o código da página do cliente atendida" como duas confirmações separadas (para não mencionar o css tweak como outro commit). Se cada uma delas levar 5 minutos para ser executada, 1/6 do meu tempo será gasto aguardando testes. Isso levaria a commits cumulativos maiores, que são mais difíceis de rastrear bugs no.

5 min de espera para executar testes de unidade? Eu sinto que os projetos em que vocês estão trabalhando devem ser divididos em componentes menores.
user441521

@ Justin catching silly mistakes (as a result of rushing)exatamente. apressar-se em geral é uma má prática em engenharia de software. Robert C. Martin recomenda escrever código como fazer uma cirurgia youtube.com/watch?v=p0O1VVqRSK0
Jerry Joseph

10

Em princípio, acho que faz sentido impedir que as pessoas façam alterações na linha principal que quebram a compilação. Ou seja, o processo para fazer alterações na ramificação principal do seu repositório deve garantir que todos os testes ainda passem. Quebrar a construção é simplesmente muito caro 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 ganchos de confirmação não é um bom plano.

  1. O desenvolvedor precisa aguardar a execução dos testes ao confirmar. Se o desenvolvedor precisar esperar em sua estação de trabalho todos os testes para passar, você desperdiçou um tempo valioso do engenheiro. O engenheiro precisa ser capaz de avançar para a próxima tarefa, mesmo que ele precise voltar porque os testes acabaram falhando.
  2. Os desenvolvedores podem querer confirmar código quebrado em uma ramificação. Em uma tarefa maior, a versão do código para desenvolvedores pode passar muito tempo não em um estado de passagem. Obviamente, mesclar esse código na linha principal seria muito ruim. Mas é bastante importante que o desenvolvedor ainda possa usar o controle de versão para acompanhar seu progresso.
  3. Ocasionalmente, existem bons motivos para ignorar o processo e ignorar os testes.

2
O nº 1 é evitado ao permitir que os desenvolvedores façam check-in em uma filial pessoal ou repositório local. É somente quando um desenvolvedor deseja seu código em algum lugar que outros desenvolvedores podem vê-lo que os testes de unidade precisam ser executados. Assim como no número 1, o número 2 é obviado apenas por ganchos nas ramificações da linha principal. O item 3 é obviado pelo fato de que A) Qualquer um desses recursos pode ser desativado, mesmo que seja um aborrecimento (e deve ser um aborrecimento) e B) Testes unitários com falha individuais podem ser desativados.
Brian

@ Brian, estou de total acordo, você pode fazer isso funcionar. Mas, tentar fazer isso bloqueando o gancho de confirmação do lado do servidor não vai funcionar.
Winston Ewert 26/10

bons pontos. Breaking the build is simply too costly in terms of lost time for all engineers on the projectGostaria de sugerir o uso de algum tipo de ferramenta de notificação de construção para evitar todos os engenheiros acabar perdendo tempo em cada construção quebrado
Jerry Joseph

3

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

Se você deseja ter uma base de código que garante a ausência de testes com falha, você pode desenvolver em ramos de recursos e puxar solicitações para o mestre. Em seguida, você pode definir pré-condições para aceitar essas solicitações pull. O benefício é que você pode executar rapidamente e os testes são executados em segundo plano.


2

Ter que esperar por compilações e testes bem-sucedidos em cada confirmação no ramo principal é realmente horrível, acho que todos concordam com isso.

Mas existem outras maneiras de obter um ramo principal consistente. Aqui está uma sugestão, um pouco semelhante à veia de check-ins fechados no TFS, mas que é generalizável a qualquer sistema de controle de versão com ramificações, embora eu use principalmente os termos git:

  • Tenha um ramo intermediário no qual você só pode confirmar mesclagens entre seus ramos dev e o ramo principal

  • Configure um gancho que inicie ou enfileire uma construção e teste as confirmações feitas na ramificação temporária, mas que não fazem o committer aguardar

  • Em compilações e testes bem-sucedidos, faça a ramificação principal avançar automaticamente se estiver atualizada

    Nota: não faça a mesclagem automática 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 elas.

Como consequência:

  • Proibir que humanos se comprometam com o ramo principal, automaticamente, se possível, mas também como parte do processo oficial, se houver uma brecha ou se não for tecnicamente viável aplicar isso

    Pelo menos, você pode garantir que ninguém o faça sem querer, ou sem malícia, uma vez que é uma regra básica. Você nunca deve tentar fazê-lo.

  • Você terá que escolher entre:

    • Uma única ramificação de temporariedade, que fará com que as mesclagens bem-sucedidas falhem, na verdade, se uma mesclagem anterior ainda não criada e não testada falhar

      Pelo menos, você saberá qual fusão falhou e alguém será corrigido, mas as mesclas entre elas não são trivialmente rastreáveis ​​(pelo sistema de controle de versão) para os resultados de novas compilações e testes.

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

    • Múltiplas ramificações temporárias, que permitirão mesclagens bem-sucedidas não conflitantes para chegar à ramificação principal

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

      Para ter fusões não conflitantes na maioria das vezes, é importante dividir as ramificações temporárias sensatamente, por exemplo, por equipe, por camada ou por componente / projeto / sistema / solução (seja qual for o nome).

      Se o ramo principal foi encaminhado para outra mesclagem, você precisará mesclar novamente. Felizmente, isso não é um problema com mesclagens não conflitantes ou com pouquíssimos conflitos.

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


Mas o objetivo de ter um ramo de trabalho não é (normalmente) garantir que você tenha uma versão estável, mas reduzir o problema causado pelos desenvolvedores trabalhando juntos no mesmo código.
Telastyn 25/10

Não se você tiver um grande repositório com grandes equipes distribuídas globalmente. Por exemplo, a Microsoft usa uma abordagem semelhante e mais em camadas com o Windows.
acelent 25/10

2

Eu prefiro que "testes de unidade passados" sejam um portão para o envio de código. No entanto, para fazer esse trabalho, você precisará de algumas coisas.

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

Você precisará de uma estrutura de teste que armazene em cache o status do teste (executado com êxito), com qualquer artefato.

Dessa forma, os check-ins com aprovação nos testes de unidade serão rápidos (verificação cruzada da origem ao artefato criado quando o desenvolvedor verificou os testes antes do check-in), aqueles com falha nos testes de unidade serão bloqueados e os desenvolvedores que Lembre-se de verificar convenientemente que as compilações antes da consolidação serão lembradas na próxima vez, porque o ciclo de compilação e teste é demorado.


1

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

Se os testes que você gosta de executar no gatilho de check-in são realmente rápidos, ou se o fluxo de trabalho do desenvolvedor forçar algum trabalho administrativo após esse check-in, acho que isso não deve importar muito e forçar os desenvolvedores a verificar apenas em coisas que absolutamente executam os testes mais básicos. (Presumo que você execute apenas os testes mais básicos nesse gatilho.)

E eu acho que, permitindo velocidade / fluxo de trabalho, é bom não enviar alterações para outros desenvolvedores que falham nos testes - e você só sabe se eles falham se os executar.

Você escreve "commit ... to remote branch" na pergunta, o que implicaria para mim que isso não é (a) algo que um desenvolvedor faz a cada poucos minutos; portanto, uma pequena espera pode ser muito bem aceitável e (b) que depois como uma confirmação, as alterações no código podem afetar outros desenvolvedores, portanto, verificações adicionais podem estar em ordem.

Posso concordar com as outras respostas em "não faça seus desenvolvedores mexerem enquanto esperam" por essa operação.


1

Confirmações quebradas não devem ser permitidas no tronco , porque o tronco é o que pode entrar em produção. Portanto, você precisa garantir que haja um gateway que eles precisam passar antes de entrar no tronco . No entanto, confirmações quebradas podem ser totalmente boas em um repositório, desde que não estejam no tronco.

Por outro lado, exigir que os desenvolvedores esperem / corrijam problemas antes de fazer alterações no repositório tem várias desvantagens.

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 relatar erros cometendo e enviando um teste com falha
  • Pressionar código incompleto permite que 2 ou mais pessoas trabalhem facilmente em um recurso em paralelo
  • Sua infraestrutura de IC pode demorar um pouco para verificar, mas o desenvolvedor não precisa esperar
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.