Faz sentido medir a cobertura condicional do código Java 8?

19

Gostaria de saber se a medição da cobertura condicional do código pelas ferramentas atuais para Java não é obsoleta desde que o Java 8 surgiu. Com o Java 8 Optionale Streammuitas vezes podemos evitar ramificações / loops de código, o que facilita obter uma cobertura condicional muito alta sem testar todos os caminhos de execução possíveis. Vamos comparar o código Java antigo com o código Java 8:

Antes do Java 8:

public String getName(User user) {
    if (user != null) {
        if (user.getName() != null) {
            return user.getName();
        }
    }
    return "unknown";
}

Existem 3 caminhos de execução possíveis no método acima. Para obter 100% de cobertura condicional, precisamos criar 3 testes de unidade.

Java 8:

public String getName(User user) {
    return Optional.ofNullable(user)
                   .map(User::getName)
                   .orElse("unknown");
}

Nesse caso, as ramificações estão ocultas e precisamos apenas de 1 teste para obter 100% de cobertura e não importa em que caso iremos testar. Embora ainda existam os mesmos três ramos lógicos que devem ser abordados, acredito. Eu acho que isso torna as estatísticas de cobertura condicional completamente não confiáveis ​​hoje em dia.

Faz sentido medir a cobertura condicional do código Java 8? Existem outras ferramentas para detectar código não testado?

Karol Lewandowski
fonte
5
As métricas de cobertura nunca foram uma boa maneira de determinar se seu código foi bem testado, apenas uma maneira de determinar o que não foi testado. Um bom desenvolvedor pensa nos vários casos em sua mente e cria testes para todos eles - ou pelo menos tudo o que ela acha importante.
Kdgregory
3
É claro que a alta cobertura condicional não significa que temos bons testes, mas acho que é uma enorme vantagem saber quais caminhos de execução são descobertos e é disso que se trata principalmente a questão. Sem cobertura condicional, é muito mais difícil identificar cenários não testados. Em relação aos caminhos: [usuário: nulo], [usuário: notnull, user.name:null], [usuário: notnull, user.name:notnull]. O que estou perdendo?
Karol Lewandowski
6
Qual é o contrato de getName? Parece que, se userfor nulo, deve retornar "desconhecido". Se usernão for nulo e user.getName()for nulo, deve retornar "desconhecido". Se usernão for nulo e user.getName()não for nulo, deve retornar isso. Então você testaria esses três casos, porque é disso que getNametrata o contrato . Você parece estar fazendo isso ao contrário. Você não deseja ver as filiais e escrever os testes de acordo com eles, deseja escrever seus testes de acordo com o seu contrato e garantir que o contrato seja cumprido. É quando você tem uma boa cobertura.
Vincent Savard
1
Novamente, não estou dizendo que a cobertura prova que meu código é perfeitamente testado, mas foi uma ferramenta extremamente valiosa que me mostrou o que não testei com certeza. Penso que os contratos de teste são inseparáveis ​​dos caminhos de execução de teste (seu exemplo é excepcional, pois envolve mecanismo de linguagem implícito). Se você não testou o caminho, não testou totalmente o contrato ou o contrato não está totalmente definido.
Karol Lewandowski
2
Vou repetir meu argumento anterior: esse é sempre o caso, a menos que você se limite apenas aos recursos básicos da linguagem e nunca chame nenhuma função que não tenha sido instrumentada. O que significa que não há bibliotecas de terceiros nem uso do SDK.
Kdgregory

Respostas:

4

Existem ferramentas que medem ramificações lógicas que podem ser criadas no Java 8?

Eu não estou ciente de nenhum. Tentei executar o código que você possui através do JaCoCo (também conhecido como EclEmma) apenas para ter certeza, mas ele mostra 0 ramificações na Optionalversão. Não conheço nenhum método de configurá-lo para dizer o contrário. Se você o configurasse para incluir também arquivos JDK, teoricamente mostraria ramificações Optional, mas acho que seria bobagem começar a verificar o código JDK. Você apenas tem que assumir que está correto.

Eu acho que a questão principal, no entanto, é perceber que as ramificações adicionais que você tinha antes do Java 8 eram, de certa forma, ramificações criadas artificialmente. O fato de eles não existirem mais no Java 8 significa apenas que agora você tem a ferramenta certa para o trabalho (neste caso Optional). No código pré-Java 8, você tinha que escrever testes de unidade extras para ter certeza de que cada ramo do código se comporta de maneira aceitável - e isso se torna um pouco mais importante em seções de código que não são triviais como o User/ getNameexemplo.

No código Java 8, você está confiando no JDK que o código funciona corretamente. Como é, você deve tratar essa Optionallinha da mesma forma que as ferramentas de cobertura de código a tratam: 3 linhas com 0 ramificações. O fato de haver outras linhas e ramificações no código abaixo é algo que você nunca prestou atenção antes, mas existe sempre que você usa algo como um ArrayListou HashMap.

Shaz
fonte
2
"Que eles não existem mais no Java 8 ..." - Não posso concordar com isso, Java 8 é compatível e ife nullainda são partes da linguagem ;-) Ainda é possível escrever código na maneira velha e passar nullusuário ou usuário com nullnome. Seus testes devem apenas provar que o contrato é cumprido, independentemente de como o método é implementado. O ponto é que não há ferramenta para dizer se você testou totalmente o contrato.
Karol Lewandowski
1
@KarolLewandowski Acho que o que Shaz está dizendo é que, se você confia em como Optional(e métodos relacionados) funcionam, não precisa mais testá-los. Não da mesma maneira que você testou um if-else: todo ifera um campo minado em potencial. Optionale expressões funcionais semelhantes já estão codificadas e garantidas para não tropeçar em você, portanto, essencialmente, há um "ramo" que desapareceu.
Andres F.
1
@AndresF. Não acho que Karol esteja sugerindo que testemos Optional. Como ele disse, logicamente ainda devemos testar se getName()lida com várias entradas possíveis da maneira que pretendemos, independentemente de sua implementação. É mais difícil determinar isso sem as ferramentas de cobertura de código, ajudando da maneira que seria antes do JDK8.
Mike Partridge
1
@ MikePartridge Sim, mas o ponto é que isso não é feito através da cobertura de agências. A cobertura da filial é necessária durante a gravação, if-elseporque cada uma dessas construções é completamente ad-hoc. Em contraste, Optional, orElse, map, etc, são todos os já testados. Os galhos, na verdade, "desaparecem" quando você usa expressões mais poderosas.
Andres F.