Teste de mutação com PITest

Teste de mutação com PITest

*1. Visão geral *

Teste de software refere-se às técnicas usadas para avaliar a funcionalidade de um aplicativo de software. Neste artigo, discutiremos algumas das métricas usadas no setor de teste de software, como* cobertura de código e teste de mutação , com interesse peculiar sobre como executar um teste de mutação usando o http://pitest .org/[ *Biblioteca PITest ].

Por uma questão de simplicidade, basearemos esta demonstração em uma função básica do palíndromo - Observe que um palíndromo é uma string que lê o mesmo para trás e para frente.

*2. Dependências do Maven *

Como você pode ver na configuração das dependências do Maven, usaremos o JUnit para executar nossos testes e a biblioteca* PITest para introduzir mutantes * em nosso código - não se preocupe, veremos em um segundo o que é um mutante. Você sempre pode procurar a versão mais recente da dependência no repositório central do maven, seguindo este link.

<dependency>
    <groupId>org.pitest</groupId>
    <artifactId>pitest-parent</artifactId>
    <version>1.1.10</version>
    <type>pom</type>
</dependency>

Para que a biblioteca PITest esteja em funcionamento, também precisamos incluir o plugin pitest-maven em nosso arquivo de configuração pom.xml:

<plugin>
    <groupId>org.pitest</groupId>
    <artifactId>pitest-maven</artifactId>
    <version>1.1.10</version>
    <configuration>
        <targetClasses>
            <param>com..testing.mutation.*</param>
        </targetClasses>
        <targetTests>
            <param>com..mutation.test.*</param>
    </targetTests>
     </configuration>
</plugin>

*3. Configuração do projeto *

Agora que temos nossas dependências do Maven configuradas, vamos dar uma olhada nesta função auto-explicativa do palíndromo:

public boolean isPalindrome(String inputString) {
    if (inputString.length() == 0) {
        return true;
    } else {
        char firstChar = inputString.charAt(0);
        char lastChar = inputString.charAt(inputString.length() - 1);
        String mid = inputString.substring(1, inputString.length() - 1);
        return (firstChar == lastChar) && isPalindrome(mid);
    }
}

Tudo o que precisamos agora é de um teste JUnit simples para garantir que nossa implementação funcione da maneira desejada:

@Test
public void whenPalindrom_thenAccept() {
    Palindrome palindromeTester = new Palindrome();
    assertTrue(palindromeTester.isPalindrome("noon"));
}

Até aqui tudo bem, estamos prontos para executar nosso caso de teste com sucesso como um teste JUnit.

Em seguida, neste artigo, vamos nos concentrar em* cobertura de código e mutação *usando a biblioteca PITest.

===* 4. Cobertura de código *

A cobertura de código tem sido amplamente utilizada na indústria de software, para medir qual porcentagem dos* caminhos de execução *foi exercida durante os testes automatizados.

Podemos medir a cobertura efetiva do código com base nos caminhos de execução usando ferramentas como* http://www.eclemma.org/index.html [Eclemma] *disponíveis no Eclipse IDE.

Depois de executar o TestPalindrome com cobertura de código, podemos obter facilmente uma pontuação de 100% de cobertura - Observe que o isPalindrome é recursivo, portanto é bastante óbvio que a verificação vazia do comprimento da entrada será coberta de qualquer maneira.

Infelizmente, as métricas de cobertura de código às vezes podem ser bastante* ineficazes , porque uma pontuação de 100% de cobertura de código significa apenas que todas as linhas foram exercitadas pelo menos uma vez, mas não diz nada sobre *testes de precisão ou casos de uso completos , e isso é por que o teste de mutação realmente importa.

*5. Cobertura de Mutações *

O teste de mutação é uma técnica de teste usada para* melhorar a adequação dos testes e identificar defeitos *no código. A idéia é alterar o código de produção dinamicamente e fazer com que os testes falhem.

_ Bons testes falharão _

Cada alteração no código é chamada de* mutante e resulta em uma versão alterada do programa, chamada de mutação *.

Dizemos que a mutação é eliminada se pode causar uma falha nos testes. Também dizemos que a mutação sobreviveu se o mutante não pudesse afetar o comportamento dos testes.

Agora vamos executar o teste usando o Maven, com a opção de objetivo definida como: org.pitest: pitest-maven: mutationCoverage.

Podemos verificar os relatórios no formato HTML no diretório target/pit-test/YYYYMMDDHHMI :

  • 100% de cobertura de linha: 7/7 *63% de cobertura de mutação: 5/8

Claramente, nosso teste varre todos os caminhos de execução, portanto, a pontuação da cobertura da linha é de 100%. Por outro lado, a biblioteca do PITest introduziu* 8 mutantes *, 5 deles foram mortos - causaram uma falha - mas 3 sobreviveram.

Podemos verificar o relatório com..testing.mutation/Palindrome.java.html para obter mais detalhes sobre os mutantes criados:

link:/wp-content/uploads/2016/07/mutations.png [imagem:/wp-content/uploads/2016/07/mutations-300x73.png [imagem, largura = 744, altura = 181]]

Estes são os mutadores ativos por padrão ao executar um teste de cobertura de mutação:

  • INCREMENTS_MUTATOR

  • VOID_METHOD_CALL_MUTATOR

  • RETURN_VALS_MUTATOR

  • MATH_MUTATOR

  • NEGATE_CONDITIONALS_MUTATOR

  • INVERT_NEGS_MUTATOR *CONDITIONALS_BOUNDARY_MUTATOR

Para mais detalhes sobre os mutadores do PITest, você pode conferir o link oficial* página de documentação *.

Nossa pontuação de cobertura de mutação reflete a falta de casos de teste , pois não podemos garantir que nossa função palíndromo rejeite entradas de sequência não palíndricas e quase palíndricas.

*6. Melhorar a pontuação de mutação *

Agora que sabemos o que é uma mutação, precisamos melhorar nossa pontuação de mutação* matando os mutantes sobreviventes *.

Vamos tomar a primeira mutação - condicional negada - na linha 6 como exemplo. O mutante sobreviveu porque, mesmo se mudarmos o trecho de código:

if (inputString.length() == 0) {
    return true;
}

To:

if (inputString.length() != 0) {
    return true;
}

O teste será aprovado e é por isso que a mutação sobreviveu . A idéia é implementar um novo teste que falhará, caso o mutante seja introduzido . O mesmo pode ser feito para os demais mutantes.

@Test
public void whenNotPalindrom_thanReject() {
    Palindrome palindromeTester = new Palindrome();
    assertFalse(palindromeTester.isPalindrome("box"));
}
@Test
public void whenNearPalindrom_thanReject() {
    Palindrome palindromeTester = new Palindrome();
    assertFalse(palindromeTester.isPalindrome("neon"));
}

Agora podemos executar nossos testes usando o plug-in de cobertura de mutação, para garantir que todas as mutações foram mortas , como podemos ver no relatório PITest gerado no diretório de destino.

  • 100% de cobertura de linha: 7/7 *100% de cobertura de mutação: 8/8

===* 7. Configuração dos testes PITest *

Às vezes, os testes de mutação podem ter muitos recursos; portanto, precisamos colocar a configuração adequada para melhorar a eficácia dos testes. Podemos fazer uso da tag* targetClasses *, para definir a lista de classes a serem mutadas. O teste de mutação não pode ser aplicado a todas as classes em um projeto do mundo real, pois será demorado e crítico em termos de recursos.

Também é importante definir os mutadores que você planeja usar durante o teste de mutação, a fim de minimizar os recursos de computação necessários para executar os testes:

<configuration>
    <targetClasses>
        <param>com..testing.mutation.*</param>
    </targetClasses>
    <targetTests>
        <param>com..mutation.test.*</param>
    </targetTests>
    <mutators>
        <mutator>CONSTRUCTOR_CALLS</mutator>
        <mutator>VOID_METHOD_CALLS</mutator>
        <mutator>RETURN_VALS</mutator>
        <mutator>NON_VOID_METHOD_CALLS</mutator>
    </mutators>
</configuration>

Além disso, a biblioteca PITest oferece uma variedade de opções disponíveis para personalizar suas estratégias de teste , você pode especificar o número máximo de mutantes introduzidos por classe usando a opção maxMutationsPerClass por exemplo. Mais detalhes sobre as opções do PITest no site oficial Maven quickstart guide .

*8. Conclusão *

Observe que a cobertura do código ainda é uma métrica importante, mas às vezes não é suficiente o suficiente para garantir um código bem testado. Portanto, neste artigo, passamos por* testes de mutação como uma maneira mais sofisticada de garantir a qualidade dos testes e endossar casos de teste, usando a biblioteca PITest *.

Também vimos como analisar os relatórios básicos do PITest e, ao mesmo tempo, melhorar o escore de cobertura de mutação .

Embora o teste de mutação revele defeitos no código, ele deve ser usado com sabedoria, porque é um processo extremamente caro e demorado .

Você pode conferir os exemplos fornecidos neste artigo no *GitHub project *.