Alternativa ao indicador "Aprovado / Quebrado"?

14

Ao ter uma integração contínua executando os testes em cada confirmação, uma prática recomendada comum é fazer com que todos os testes passem o tempo todo (também conhecido como "não quebre a compilação").

Encontro alguns problemas com isso:

Por exemplo, não se pode ajudar um projeto de código aberto criando testes correspondentes a tickets. Sei que se eu propuser uma solicitação de recebimento a um projeto de código aberto que contenha teste de falha, a compilação será marcada como falha e o projeto não desejará que ela seja mesclada em seu repositório porque "interromperá a compilação".

E não acredito que seja ruim ter testes reprovados em seu repositório , é como ter problemas em aberto no seu rastreador. Essas são apenas coisas que estão esperando para serem consertadas.

O mesmo vale para uma empresa. Se você trabalha com TDD, não pode escrever testes, confirmar e depois escrever o código lógico que realiza o teste. Isso significa que, se eu escrevi de 4 a 5 testes no meu laptop, não posso executá-los antes de sair de férias. Ninguém pode retomar o meu trabalho. Eu não posso nem "compartilhá-los" com um colega, exceto enviando-os por e-mail, por exemplo. Também impede trabalhar com uma pessoa escrevendo os testes e a outra escrevendo o modelo.

Tudo isso para dizer, estou usando mal / entendendo mal o processo de compilação / integração contínua? Parece-me que "passar" / "não passar" é um indicador muito restrito.

Existe uma maneira de tornar a integração contínua e compatível com TDD?

Talvez exista uma solução / prática padrão para distinguir "novos testes" (que podem falhar) e "testes de regressão" (que não devem falhar porque costumavam trabalhar)?

Matthieu Napoli
fonte
1
Tenha um indicador que mostre se o número de testes com falha subiu (vermelho) ou diminuiu (verde) na última hora (mais ou menos).
Joachim Sauer
2
Não sou especialista em TDD / ALM (daí o comentário, e não uma resposta), mas acho que seu problema pode ser resolvido com ramos particulares / ramos de recursos. Você está trabalhando no recurso A? Ramifique-o, trabalhe-o na ramificação (com colegas) e, quando terminar - junte-o ao tronco continuamente integrado.
Avner Shahar-Kashtan
@JoachimSauer Sim, mas essa métrica é padronizada / usada em algum projeto importante? Estou tentando entender por que a maioria dos projetos (e ferramentas de CI) funciona dessa maneira.
Matthieu Napoli
Acho que o critério correto para "testes que podem falhar" não é "novos testes", mas "testes para problemas abertos conhecidos". Posso ver como esses testes são úteis - também como esses testes NÃO são úteis na criação do IC porque poluem o significado da aprovação / reprovação no teste (você só deseja executar testes nos quais alguém realmente passou algum tempo para fazê-los passar).
Joris Timmermans
@MadKeithV Exatamente
Matthieu Napoli

Respostas:

12

Vejo onde você está chegando, mas esses tipos de problemas geralmente são resolvidos de outras maneiras. Há uma boa razão pela qual esse é o protocolo padrão. Se alguém enviar um código que não seja compilado, todos que atualizarem seu código terão um programa que não será compilado . Isso inclui programadores que atualmente estão trabalhando em algo completamente diferente e, de alguma forma, se encontram em uma situação em que precisam esperar antes de poderem compilar e testar o que estão trabalhando.

O protocolo padrão é que você pode confirmar as alterações mesmo para o trabalho completo ou até incompleto, desde que seja compilado para que os programadores possam atualizar seu código todos os dias, se necessário.

No entanto, ainda vejo o que você está recebendo. Às vezes, você deseja confirmar para salvar seu código. Para isso, a maioria dos repositórios de origem suporta ramificação. Isso permite que você crie uma ramificação privada, trabalhe nela sem incomodar outras pessoas e, em seguida, entre no tronco quando o trabalho estiver concluído. Isso permite que você efetue o commit quando quiser, sem nenhuma das folgas associadas à quebra da compilação.

Se isso não for adequado, o GIT permite confirmar (enviando) para repositórios em sua máquina local, mas é possível que o repositório esteja em qualquer lugar. Você pode criar um repositório para trabalho potencialmente parcial / incompleto e outro repositório para o trabalho finalizado; nesse repositório, você pode adicionar uma construção noturna.

Mais uma vez, não posso enfatizar a importância o suficiente. Nunca cometa código quebrado no tronco! Suas contribuições não podem afetar o trabalho de outros programadores.

Editar

Vejo que você pretendia testes quebrados, mas, na minha humilde opinião, há pouca diferença. O objetivo de um teste é determinar se um aspecto específico de um programa passa ou falha. Se ele sempre falha e você não faz nada, o teste, no uso tradicional do teste de unidade, não serve para nada. Se você usá-lo para executar alguma outra métrica que não implica necessariamente uma confirmação "com falha" se um desses testes falhar, eu recomendo fortemente que você encontre outra maneira de fazer a mesma coisa.

Caso contrário, você corre o risco de o teste nunca ser levado em consideração ou, se causar falha na sua compilação, que seus colegas programadores ignorem as compilações com falha. É mais importante que os programadores percebam quando quebraram uma compilação do que realizar um teste que não oferece uma visão real e pode resultar apenas em práticas inadequadas.

Neil
fonte
1
Na verdade, você faz questão de se relacionar com tópicos. Mas nunca estou falando de cometer código quebrado, apenas de falhar nos testes. Por exemplo, eu poderia ajudar um projeto de código aberto criando testes para tickets recebidos, mesmo que não saiba como corrigi-los. Isso economiza algum tempo para os mantenedores.
Matthieu Napoli
Se estou trabalhando no mesmo projeto que você e você faz o upload de um teste com falha, agora tenho uma compilação que possui um teste com falha. Eu posso acabar excluindo seu teste, já que essa funcionalidade ainda não está implementada, ou decidir implementar o recurso e acabar pisando no seu código e perdendo tempo. Se houvesse uma cultura de fazer isso, esse tipo de resposta poderia ser evitado, mas todo mundo faria isso, e mesmo quando todos os seus testes passam, nem todos os meus fazem. Nesse caso, a construção sempre apresentava testes com falha. Eu não vejo um lado positivo.
Michael Shaw
the build would always have failing testsprecisamente! Mas isso é uma coisa tão ruim? Nossa única métrica é "a compilação está corrompida ou não", mas seu código pode estar cheio de bugs conhecidos , de modo que isso realmente não significa nada, exceto que não há regressão. Em um mundo perfeito, todos os problemas dos rastreadores teriam um teste (reproduzir é mais fácil do que consertar). Portanto, a vantagem seria ver que 35 testes / 70% de todos os testes estão passando, que o Branch-A o aprimora para 40 testes (80%) sem regressão e que o Branch-B tem regressões. Hoje, você só pode dizer que o Mestre e o Ramo A estão OK e o Ramo B está quebrado.
Matthieu Napoli
@ Matthieu Eu vejo o que você está recebendo. Parece que você precisa de uma categoria especial ou algo que diga "ei, se esse teste está falhando, tudo bem. Nós sabemos. Mas ainda queremos que ele seja executado e, se for aprovado, é ainda melhor e devemos remover a categoria especial porque agora nos importamos se ele quebrar "
Earlz
@Earlz Exatamente! O que eu queria saber é "isso é feito por alguém em algum lugar? E existem ferramentas que suportam isso (bibliotecas de CI e testes de unidade?") Porque se eu apenas categorizasse esses testes com ferramentas clássicas de CI e testes de unidade, a compilação será sempre falha de qualquer maneira e não verei uma diferença entre quais testes falharam e, portanto, não será útil: /
Matthieu Napoli
4

Dada uma ramificação principal com testes falhos, como você pode ter certeza - sem comparar essa lista com compilações anteriores - que não introduziu bugs?

Simplesmente acompanhar o número de testes com falha é insuficiente: você pode corrigir um teste e interromper outro. E se você estiver de férias, não ficará claro para outras pessoas que estão olhando para a falha na construção.

Mantenha seu ramo mestre sempre limpo e verde . Trabalhe em um galho. Mantenha a filial sob o IC, em um trabalho separado, e faça testes reprovados no conteúdo do seu coração. Só não quebre o mestre.

Faça com que o revisor da ramificação apenas mescle sua ramificação se ela passar em todos os testes. (Mais fortemente: tenha o revisor capaz de mesclar sua ramificação se o resultado da fusão da ramificação no mestre passar em todos os testes!)

Frank Shearar
fonte
2
Simply tracking the number of failing tests is insufficientessa não é a única métrica possível. Por exemplo: Branch-A improves it to 40 tests (80% passing) with no regression. Nenhuma regressão significa que os testes anteriores sempre passam. Em resumo, um teste poderá falhar desde que nunca tenha passado. Parece-me que estamos perdendo coisas boas ao restringir a proibição de testes com falha nos ramos principais. (é claro que isso exigiria ferramentas de trabalho de forma diferente: testes unitários, CI, ...)
Matthieu Napoli
Ainda defendo meu argumento: o mestre deve sempre ser verde, porque é claro e inequívoco. Por todos os meios, os testes de marcadores falharam ... em um ramo de recurso. O CI pode lembrar as pessoas de bugs pendentes.
9753 Frank Shearar
Penso que o que Matthieu propõe é uma definição ligeiramente diferente de "verde", sem se desviar do mestre sempre sendo verde. Não é óbvio para mim que isso não faz sentido - seria necessário algumas ferramentas não inteiramente triviais para rastreamento, é claro. (Necessidade de reverter uma mudança que fez esse teste passar Azar se isso significa que o estado é subitamente vermelho ...?)
Christopher Creutzig
NUnit tem o conceito de um teste ignorado. Isso pode ser uma alternativa: eles não são executados para não falharem, mas ainda são relatados como ignorados.
Frank Shearar
2

Existem maneiras de resolver seus problemas sem descartar práticas bem compreendidas e aceitas sobre integração contínua.

Começarei com o problema de cometer um 'teste quebrado' que corresponde a um ticket. Uma solução é criar um ou mais testes de quebra expondo o problema e, na verdade, corrigi- lo, para que possam ser mesclados novamente à linha de código principal. A segunda solução é fazer os testes interrompidos, mas use algum tipo de sinalizador de ignorar para que eles não executem e quebrem a compilação. Possivelmente, adicione um comentário ou uma anotação especial que torne muito óbvio que esse é um teste quebrado Ticket#N. Anexe também uma nota ao próprio ticket que se refere aos testes criados que estão aguardando para serem ignorados e executados. Isso ajudaria uma pessoa a consertar o ticket, mas também não seria uma bandeira vermelha para alguém que passasse pelo teste.

E para o seu próximo problema com o TDD. TDD é sobre escrever um pequeno teste e depois escrever um pequeno pedaço de código para fazer esse teste passar . Continue iterando até ter um pequeno módulo funcional. Eu sinto que se você escrever 4-5 testes e sair de férias, poderá estar fazendo errado. Você pode emparelhar o programa com alguém de uma maneira que um de vocês escreva o teste e o outro o código correspondente. No entanto, você não deve usar o repositório da linha de código principal para compartilhar esse código entre vocês dois antes que um módulo completo esteja pronto para ser confirmado. Como outros sugeriram, um ramo compartilhado resolveria seus problemas lá.

Tentar quebrar o mantra de integração contínua pode levar a caminhos inesperados e assustadores. Por exemplo, o que significaria cobertura de código nesse tipo de ambiente ? Como os desenvolvedores não acham que o sistema possui muitos " Windows Quebrados " ? Como alguém faria uma alteração, executaria o teste e saberia se eles estão realmente quebrando algo novo, ou se são apenas coisas antigas?

c_maker
fonte
Você não precisa de ferramentas para compartilhar com a pessoa com quem está programando em pares - basta entregar o teclado. Se você estiver usando computadores diferentes, bem, não há nada de errado nisso, simplesmente não é "programação em pares".
22413 Christopher Creutzig
1

Eu acho que seu problema fundamental é que você está incluindo os resultados do teste como parte da compilação. Enquanto obviamente algumas pessoas concordam com você, outras não. Quebrar a compilação ocorre quando não é compilado. Não quando não cria sem erros.

Considere um projeto importante como o Windows ou Linux, ou mesmo algo como o Firefox - você acha que eles são enviados sem erros? Claro que não. Agora, esses projetos não estão executando o TDD, mas isso é realmente irrelevante - o TDD não muda dois fatos fundamentais: existem erros e leva tempo para corrigi-los. Tempo que um projeto (código aberto ou não) simplesmente não pode perder com bugs de baixa prioridade. O KDE recentemente corrigiu um bug com mais de uma década. Quando foi a última vez que você ouviu alguém dizer "Fico feliz que esperamos uma década para lançar nosso projeto"?

De certa forma, o TDD provavelmente facilita o envio de bugs - porque você entende melhor qual é a falha. Se você pode definir com precisão o que causa o erro, você tem uma excelente base para avaliar o custo de corrigi-lo.

Minha recomendação é encontrar um projeto que não se importe com o vermelho entre o verde.

jmoreno
fonte
1
 > a common best practice is to have all the tests passing (green) at all times.

Prefiro que todos os testes não falhem (não são vermelhos).

Com essa definição um pouco diferente, você também pode definir testes que são

  • ainda não implementado (cinza na unidade, se houver uma NotImplementedException)
  • conhecido por falhar = "precisa ser feito" marcando / anotando o teste como ignorado (amarelo)

Se você checá-las no repositório, sua construção contínua não está quebrada e, portanto, é válida.

k3b
fonte
0

Você pode considerar dois conceitos diferentes de construção de IC.

  1. Compilações regulares de IC. A criação regular do IC deve compilar e executar apenas os testes para os quais o código foi gravado para fazê-los passar, para que os relatórios de aprovação / reprovação do IC sejam um indicador claro e inequívoco de regressão em relação ao estado do código aceito anteriormente.
  2. Uma criação de IC "futura". Essa compilação compila e executa apenas os testes para os quais nenhum código foi escrito especificamente para fazê-los passar. Pode haver vários motivos para fazer esse teste:

    • É possível adicionar testes para casos de falha específicos no rastreador de problemas, para os quais ainda não foi tentada nenhuma correção. É claramente útil já ter um teste em execução codificado para um problema, mesmo sem uma correção.

    • Testes adicionados para a nova funcionalidade necessária que ainda não foi implementada.

    • Freqüentemente, é mais fácil saber como testar uma falha ou recurso do que implementar ou corrigir o recurso, e separar as duas etapas submetendo o teste ao controle de origem pode ser útil para garantir que nenhuma informação seja perdida.

Como no IC "padrão", no regime de desenvolvimento regular, a equipe só observava os resultados diários da compilação regular.

A equipe também pode acompanhar a evolução dos casos de teste a partir da compilação "futura" do IC - especificamente para verificar se alguma alteração feita no IC regular realmente corrige problemas da compilação "futura", o que pode ser uma indicação de uma importante problema subjacente ou melhoria do design.

Por fim, se um membro da equipe tiver mais tempo disponível, poderá corrigir um dos problemas do "futuro" e, se conseguir, migrá-lo para "regular" (enquanto atualiza o status do rastreador de problemas).

Joris Timmermans
fonte
0

E não acredito que seja ruim ter testes reprovados em seu repositório, é como ter problemas em aberto no seu rastreador. Essas são apenas coisas que estão esperando para serem consertadas.

O problema não está falhando nos testes, é um indicador de regressão simples e sem contexto. Aka: como desenvolvedor, posso verificar um único indicador e saber se introduzi um código de regressão ou quebra.

No momento em que você introduzir o conceito de falhas "leves" (tudo bem, estamos trabalhando nisso / ainda não implementado / aguardando a nova versão / ela passará novamente assim que a outra compilação for corrigida), você precisará todo mundo que pode executar ou examinar o teste para saber o estado esperado. O que em uma equipe grande o suficiente mudará a cada hora: seu indicador se torna sem sentido. Em um contexto menor (teste de integração privada da equipe, por exemplo), acho que é como uma dívida técnica e está bem - só precisa ser gerenciado.

A maneira como a maioria das ferramentas aborda isso é 'verde / passante' reflete qual é o resultado esperado - não que o código esteja funcionando:

  • Fitness tem o conceito de falha esperada.
  • A JUnit possui @Ignored / @Test (expect =)
  • O pepino tem o status 'ainda não implementado' (ou como ele é chamado)

Aqueles vêm com seus próprios problemas (como você distingue entre 'sim, sabemos que está quebrado, trabalhando nisso' e 'o comportamento correto é uma exceção') - mas eles ajudam.

ptyx
fonte
0

Eu uso testes ignorados.

Na estrutura de teste de unidade específica que eu uso, posso gerar uma exceção SkipTest. O teste não é realmente executado e sua falha não interrompe a compilação. No entanto, posso ver o número de testes ignorados e ver se há trabalho a ser feito nessa área.

Winston Ewert
fonte