Me siga no X | Me siga no LinkedIn | Apoie a Newsletter | Solicite uma consultoria
Teste de unidade é uma das principais atividades do processo de desenvolvimento. O teste de unidade foca em testar pequenas partes isoladas de um programa, com o objetivo de garantir que cada unidade atenda às especificações esperadas.
Através da cultura de escrita de testes de unidade, é possível detectar erros precocemente, além de facilitação a depuração e melhorar. Alguns pesquisadores sugerem, inclusive, que testes de unidade auxiliam na documentação do programa.
No entanto, criar bons testes de unidade é ainda um desafio. Primeiro pois criar testes de unidade ainda é um processo inerentemente manual. Embora existam várias ferramentas criadas com esse propósito, poucas delas se tornaram populares no arsenal da pessoa desenvolvedora. Segundo, pois a criação de testes de unidade capaz de revela bus requer um bom entendimento do código a ser testado. Isso pode ser especialmente desafiador quando se lida com código complexo.
Por conta destas dificuldades, não é incomum que pessoas desenvolvedoras que estejam trabalharam em ritmo acelerado optem por não escrever testes de unidade.
Com os recentes avanços e aperfeiçoamentos dos LLMs que geram código, é natural esperar que eles sejam também capazes de gerar testes de unidade. No entanto, pouco ainda se sabe sobre o potencial dos LLMs em criar de testes de unidade que sejam, de fato, efetivos.
Para ajudar a esclarecer esse questionamento, um grupo de pesquisadores realizou um estudo avaliando a capacidade de três LLMs (Codex, ChatGPT3.5 e CodeGen) em gerar teste de unidade para programas Java usando o JUnit5. Os testes gerados foram avaliados em termos da taxa de compilação, corretude, cobertura e design de qualidade de testes.
Como o estudo foi feito?
Em primeiro lugar, os pesquisadores buscaram um conjunto de classes Java para se gerar os testes de unidade com JUnit5. Selecionaram 160 classes de um dataset “HumanEval”, e complementaram com outras 194 outras classes vindas de 47 projetos de software livre (organizadas no dataset chamado “SF110”).
Em seguida, os pesquisadores utilizaram três LLMs para gerar os testes de unidade. Mais especificamente:
Codex: O Codex é um LLM de 12 bilhões de parâmetros, descendente do modelo GPT-3. O estudo utilizou o modelo “code-davinci-002”, um dos mais completos disponíveis pela OpenAI. Codex, inclusive, é o modelo usado como infraestrutura base no GitHub. Codex aceita até 8K tokens de entrada e produz, no máximo, 4K tokens de saída.
CodeGen: O CodeGen é, na verdade, um grupo de três modelos para geração de código. O primeiro (CodeGen-NL) foi treinado principalmente em linguagem natural, o segundo (CodeGen-Mono) treinado somente em código Python, enquanto o terceiro (CodeGen-Multi) foi treinado em C, C++, Go, Java, JavaScript e Python. O artigo utilizou o modelo CodeGen-Multi com 350 milhões de parâmetros. O CodeGen trabalha com no máximo 2k tokens, que devem ser distribuídos entre entrada e saída.
GPT3.5: O terceiro LLM, GPT-3.5-turbo, alimenta o chatbot ChatGPT. Este LLM Permite interações de ida e volta e pode ser instruído para gerar código. O GPT3.5 recebe até 4K tokens de entrada e produz no máximo 2K tokens de saída.
Os pesquisadores instruíram os LLMs para criar 10 testes de unidade para uma das 354 classes selecionadas. Os prompts foram executados com a variável temperatura definida como zero, pois isto torna o resultado dos prompts menos variáveis e mais determinístico.
Todos os mais de 5 mil testes gerados nessa pesquisa foram manualmente executados.
Métricas de avaliação dos testes gerados por LLMs
Para avaliar a qualidade dos testes gerados, foram utilizadas diversas métricas:
Se calculou a cobertura de código exercitado pelos testes, tanto cobertura de linhas quanto cobertura de branches. Os pesquisadores utilizaram a ferramenta JaCoCo para fazer essa metrificação.
Se avaliou a corretude dos testes, através da contagem dos testes que falharam durante a execução. Como o estudo assumiu que as classes selecionadas estavam corretamente implementadas, uma falha na execução do teste indicaria uma falha no implementação do teste.
Se avaliou a qualidade dos testes, em termos da presença de bad smells nos testes. Bad smells em código de teste podem potencialmente indicar um problema, ineficiências ou más práticas de programação ou design de testes. Os autores usaram a ferramenta TsDetector para identificar esses bad smells.
O que foi descoberto?
Os resultados foram organizados em termos do 1) status de compilação dos programas, 2) corretude das soluções, 3) cobertura dos testes, e 4) design de teste.
Compilação dos programas
Surpreendentemente, para o dataset do HumanEval, menos de 50% dos testes gerados por todos os modelos eram compiláveis. Em particular, no CodeGen, apenas 24% dos testes gerados eram compiláveis. Por outro lado, 44% dos testes gerado pelo Codex eram compiláveis—a maior taxa de compilação dentre todos os modelos.
Quando considerado as classes que advinham dos projetos de software livre, a taxa de compilação foi ainda menor: cerca de 2.7% a 21% dos testes gerados entre todos os modelos eram compiláveis. Curiosamente, um comportamento diferente do dataset HumanEval foi observado neste dataset de classes de projetos de software livre: 21% dos testes do CodeGen era compiláveis, enquanto 2.7% dos testes do Codex eram compiláveis.
Ao analisar os testes de unidade gerados, os pesquisadores notaram diversos pequenos problemas de código que tornavam o código não compilável. Alguns desses problemas:
Testes com loops infinitos
Testes que incluíam expressões em linguagem natural
Testes incompletos, devido ao LLM ter chegado ao seu máximo uso de tokens
Ao manualmente corrigir alguns destes problemas, os pesquisadores observaram que a taxa de compilação melhorou significativamente. Por exemplo, para o dataset do HumanEval, a taxa de testes compiláveis no Codex subiu de 44% para cerca de 99%. No segundo dataset com classes de projetos de software livre, a taxa de testes compiláveis também aumentou significativamente. Por ex, o Codex passou de 2.7% para 74% de testes compiláveis. O ChatGPT teve uma melhoria similar: subiu de 10% para 86% de testes compiláveis no dataset de projetos de software livre.
Dos 4.342 testes gerados para o primeiro dataset, 978 eram compiláveis. Para o segundo dataset, dos 2.022 testes gerados, 600 eram compiláveis.
O que aprendi com isso? Embora vários testes gerado por LLMs contenham erros de compilação, uma porção significante desses testes pôde ser corrigida com correções simples.
Corretude das soluções
O trabalho criou dois categorias para avaliar a corretude dos testes:
Testes corretos: se refere a classes de testes em que 100% dos seus métodos de testes estão passando
Testes “um tanto” corretos: se refere a classes de testes que tem pelo menos um teste passando.
Usando essas métricas, para o dataset HumanEval, os pesquisadores notaram que 78% dos testes gerados pelo Codex eram corretos (ou seja, 100% dos métodos passavam). Por outro lado, apenas 24% dos testes do CodeGen eram corretos. Por outro lado, cerca de 52% dos testes do ChatGPT eram corretos. Ademais, o ChatGPT teve um desempenho melhor entre os demais modelos, na métrica de testes “um tanto” corretos: 92% dos testes gerados tinham ao menos um testes passando.
Por outro lado, no dataset de classes de projetos de software livre, o Codex teve novamente o melhor desempenho (46% dos testes gerados eram corretos), enquanto que o ChatGPT teve o pior desempenho, gerando somente 7% de testes corretos.
O que aprendi com isso? Embora seja mais simples utilizar a interface do ChatGPT, para tarefas de geração de testes de unidade, é melhor usar seus colegas, como o Codex.
Cobertura dos testes
Para o dataset HumanEval, os pesquisadores observaram que os testes de unidade gerados por LLMs tinham cobertura de linha entre 58% e 87% e cobertura de ramos entre 54% e 92%. Em particular, o Codex obteve melhor resultado em ambas a métricas: 87% de cobertura de linha e 92% de cobertura de ramos.
Curiosamente, a cobertura dos testes gerados por LLMs foi menor do que a cobertura de testes da ferramenta EvoSuite (ferramenta acadêmica que utiliza inteligência artificial para geração de testes de unidade). O EvoSuite obteve 96% de cobertura de linhas e 94% de cobertura de branches.
Por outro lado, os resultados foram drasticamente diferentes quando avaliados no dataset de classes de projetos de software livre. Nesse dataset, tanto o ChatGPT quanto o CodeGen obtiveram 0% de cobertura de linhas, e menos de 1% de cobertura de ramos. O Codex, por outro lado, teve desempenho ligeiramente superior, obtendo 2.5% em cobertura de linha e 1.4% em cobertura de ramo. A EvoSuite foi melhor disparada nesse critério. Para esse conjunto de classes, obteve 27% para cobertura de linhas e, novamente, 27% para cobertura de ramos.
O que aprendi com isso? Novamente, aparentemente usar o ChatGPT para gerar testes não parece uma boa ideia. Ademais, testes de unidade gerados pela EvoSuite ainda tem cobertura de código significativamente superior do que daqueles gerados por LLMs.
Design de teste
A presença de bad smells no design do teste de código foi a última forma de avaliação dos testes gerados por LLMs.
De maneira geral, observou-se com bastante frequência a existência de três tipos de bad smells em as abordagens: Magic Number, Assertion Roulete e oLazy Test.
O Magic Number acontece quando se utiliza valores hardcoded em uma asserção sem explicar o seu significado. Por exemplo, no exemplo abaixo1, os números
15
,3
e7
, que são passados para o métodoLargestDivisor.largestDivisor
poderiam ter sido armazenados em uma variável com nome mais descritivo.O Assertion Roulette acontece quando se utiliza várias asserções dentro de um mesmo caso de teste; nesse caso, em caso de falha no teste, é difícil de imediatamente distinguir a causa da falha.
O Lazy Test, por fim, acontece quando múltiplos métodos de testes invocam o mesmo método de produção.
@Test
void testLargestDivisor() {
assertEquals(5, LargestDivisor.largestDivisor(15));
assertEquals(1, LargestDivisor.largestDivisor(3));
assertEquals(1, LargestDivisor.largestDivisor(7));
}
O Magic Number acontece em 100% dos testes gerados pelo Codex e ChatGPT, e em 91% dos testes gerados pelo CodeGen. O Lazy Test, por outro lado, acontece em 86% dos testes do ChatGPT, 41% dos testes gerados pelo Codex, e 17% dos testes do CodeGen. Por fim, o Assertion Roulete acontece em cerca de 60% dos testes gerados pelo Codex, mas somente 24% dos testes gerados pelo ChatGPT.
Conclusão
Embora os LLMs sejam capazes de gerar testes de unidade, parece que ainda existe um longo percursos para que essas ferramentas possam gerar testes que sejam, de fato, efetivos. Copiar e colar um teste gerado por um LLM na nossa IDE favorita ainda não parece factível, sem ao menos realizar uma série de ajustes e verificações.
Todavia, talvez possamos tirar vantagem (e diminuir a frustração) com essas ferramentas se enxergarmos os testes gerados como templates e não como solução; ou seja, parece que pedir para um LLM gerar algo base e construir em cima disso seja mais realístico.
Quais são as suas percepções? Já teve experiências melhores na criação de testes de unidade usando LLMs? Comenta aqui! 👇
Exemplo removido do artigo https://arxiv.org/pdf/2305.00418.pdf