TDD com GitHub Copilot
por Paulo Sobocinski
O advento de assistentes de codificação de IA, como o GitHub Copilot, significará que não precisaremos de testes? O TDD se tornará obsoleto? Para responder a isso, vamos examinar duas maneiras pelas quais o TDD ajuda no desenvolvimento de software program: fornecendo um bom suggestions e um meio de “dividir e conquistar” ao resolver problemas.
TDD para um bom suggestions
Um bom suggestions é rápido e preciso. Em ambos os aspectos, nada se compara a começar com um teste unitário bem escrito. Nem testes manuais, nem documentação, nem revisão de código e, sim, nem mesmo IA generativa. Na verdade, os LLMs fornecem informações irrelevantes e até alucinar. O TDD é especialmente necessário ao usar assistentes de codificação de IA. Pelas mesmas razões que precisamos de suggestions rápido e preciso sobre o código que escrevemos, precisamos de suggestions rápido e preciso sobre o código que nosso assistente de codificação de IA escreve.
TDD para problemas de dividir e conquistar
A resolução de problemas através da divisão para conquistar significa que problemas menores podem ser resolvidos mais cedo do que problemas maiores. Isso permite integração contínua, desenvolvimento baseado em tronco e, por fim, entrega contínua. Mas será que realmente precisamos de tudo isso se os assistentes de IA fizerem a codificação para nós?
Sim. LLMs raramente fornecem a funcionalidade exata de que precisamos após um único immediate. Portanto, o desenvolvimento iterativo ainda não irá desaparecer. Além disso, os LLMs parecem “provocar raciocínio” (ver estudo vinculado) quando resolvem problemas de forma incremental por meio de estímulo de cadeia de pensamento. Os assistentes de codificação de IA baseados em LLM têm melhor desempenho quando dividem e conquistam problemas, e TDD é como fazemos isso para o desenvolvimento de software program.
Dicas de TDD para GitHub Copilot
Na Thoughtworks, usamos o GitHub Copilot com TDD desde o início do ano. Nosso objetivo tem sido experimentar, avaliar e desenvolver uma série de práticas eficazes em torno do uso da ferramenta.
0. Primeiros passos
Começar com um arquivo de teste em branco não significa começar com um contexto em branco. Freqüentemente começamos com uma história de usuário com algumas notas aproximadas. Também conversamos sobre um ponto de partida com nosso parceiro de emparelhamento.
Todo esse contexto é que o Copilot não “vê” até que o coloquemos em um arquivo aberto (por exemplo, no topo do nosso arquivo de teste). O Copilot pode trabalhar com erros de digitação, forma pontual e gramática ruim – você escolhe. Mas não funciona com um arquivo em branco.
Alguns exemplos de contexto inicial que funcionaram para nós:
- Maquete de arte ASCII
- Critérios de Aceitação
- Suposições orientadoras como:
- “Não é necessária interface gráfica”
- “Use Programação Orientada a Objetos” (vs. Programação Funcional)
O Copilot usa arquivos abertos para contexto, portanto, manter o arquivo de teste e de implementação abertos (por exemplo, lado a lado) melhora muito a capacidade de conclusão de código do Copilot.
1. Vermelho
Começamos escrevendo um nome de exemplo de teste descritivo. Quanto mais descritivo for o nome, melhor será o desempenho do preenchimento de código do Copilot.
Achamos que um Dado-Quando-Então a estrutura ajuda de três maneiras. Primeiro, nos lembra de fornecer contexto de negócios. Em segundo lugar, permite que o Copilot forneça recomendações de nomenclatura ricas e expressivas para exemplos de teste. Terceiro, revela a “compreensão” do problema pelo Copilot a partir do contexto do topo do arquivo (descrito na seção anterior).
Por exemplo, se estivermos trabalhando no código de back-end e o Copilot estiver completando o código do nome do nosso exemplo de teste, “dado ao usuário… clica no botão comprar”isso nos diz que devemos atualizar o contexto do topo do arquivo para especificar, “suponha que não haja GUI” ou, “este conjunto de testes faz interface com os endpoints da API de um aplicativo Python Flask”.
Mais “pegadinhas” a serem observadas:
- O Copilot pode completar vários testes de código ao mesmo tempo. Esses testes geralmente são inúteis (nós os excluímos).
- À medida que adicionamos mais testes, o Copilot completará o código em várias linhas em vez de uma linha por vez. Freqüentemente, ele inferirá as etapas corretas de “organizar” e “agir” a partir dos nomes dos testes.
- Aqui está a pegadinha: ele infere a etapa correta de “afirmação” com menos frequência, por isso tomamos cuidado especial aqui para que o novo teste seja falhando corretamente antes de passar para a etapa “verde”.
2. Verde
Agora estamos prontos para o Copilot ajudar na implementação. Um conjunto de testes já existente, expressivo e legível maximiza o potencial do Copilot nesta etapa.
Dito isto, o Copilot muitas vezes falha em dar “passos de bebê”. Por exemplo, ao adicionar um novo método, o “child step” significa retornar um valor codificado que passa no teste. Até o momento, não conseguimos persuadir o Copilot a adotar essa abordagem.
Testes de preenchimento
Em vez de dar “passos de bebê”, o Copilot dá um salto à frente e fornece funcionalidades que, embora muitas vezes relevantes, ainda não foram testadas. Como solução alternativa, “preenchemos” os testes ausentes. Embora isso divirja do fluxo padrão do TDD, ainda não vimos nenhum problema sério com nossa solução alternativa.
Excluir e regenerar
Para código de implementação que precisa de atualização, a maneira mais eficaz de envolver o Copilot é excluir a implementação e fazer com que ele gere novamente o código do zero. Se isso falhar, excluir o conteúdo do método e escrever a abordagem passo a passo usando comentários de código pode ajudar. Caso contrário, o melhor caminho a seguir pode ser simplesmente desligar o Copilot momentaneamente e codificar a solução manualmente.
3. Refatorar
Refatorar no TDD significa fazer mudanças incrementais que melhoram a capacidade de manutenção e a extensibilidade da base de código, tudo executado preservando o comportamento (e uma base de código funcional).
Para isso, consideramos a capacidade do Copilot limitada. Considere dois cenários:
- “Eu conheço o movimento de refatoração que quero tentar”: Atalhos de refatoração IDE e recursos como seleção de vários cursores nos levam aonde queremos mais rápido do que o Copilot.
- “Não sei qual movimento de refatoração tomar”: A conclusão do código copiloto não pode nos guiar através de uma refatoração. No entanto, o Copilot Chat pode fazer sugestões de melhoria de código diretamente no IDE. Começamos a explorar esse recurso e vemos a promessa de fazer sugestões úteis em um escopo pequeno e localizado. Mas ainda não tivemos muito sucesso em sugestões de refatoração em larga escala (ou seja, além de um único método/função).
Às vezes conhecemos o movimento de refatoração, mas não sabemos a sintaxe necessária para realizá-lo. Por exemplo, criar uma simulação de teste que nos permitiria injetar uma dependência. Para essas situações, o Copilot pode ajudar a fornecer uma resposta em linha quando solicitado por meio de um comentário de código. Isso nos poupa da mudança de contexto para documentação ou pesquisa na internet.
Conclusão
O ditado comum, “entra lixo, sai lixo” se aplica tanto à Engenharia de Dados quanto à IA Generativa e LLMs. Dito de outra forma: insumos de maior qualidade permitem que a capacidade dos LLMs seja melhor aproveitada. No nosso caso, o TDD mantém um alto nível de qualidade de código. Essa entrada de alta qualidade leva a um melhor desempenho do Copilot do que seria possível de outra forma.
Portanto, recomendamos o uso do Copilot com TDD e esperamos que as dicas acima sejam úteis para fazer isso.
Graças à equipe “Ensembling with Copilot” iniciada na Thoughtworks Canadá; eles são a principal fonte das descobertas abordadas neste memorando: Om, Vivian, Nenad, Rishi, Zack, Eren, Janice, Yada, Geet e Matthew.