¿Tiene sentido medir la cobertura condicional para el código Java 8?

19

Me pregunto si medir la cobertura de código condicional mediante las herramientas actuales para Java no es obsoleto desde que surgió Java 8. Con Java 8 Optionaly a Streammenudo podemos evitar ramas / bucles de código, lo que facilita obtener una cobertura condicional muy alta sin probar todas las rutas de ejecución posibles. Comparemos el antiguo código Java con el código Java 8:

Antes de Java 8:

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

Hay 3 posibles rutas de ejecución en el método anterior. Para obtener el 100% de la cobertura condicional, necesitamos crear 3 pruebas unitarias.

Java 8:

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

En este caso, las ramas están ocultas y solo necesitamos 1 prueba para obtener una cobertura del 100% y no importa en qué caso probaremos. Aunque todavía hay las mismas 3 ramas lógicas que deberían cubrirse, creo. Creo que hace que las estadísticas de cobertura condicional sean completamente desconfiadas en estos días.

¿Tiene sentido medir la cobertura condicional para el código Java 8? ¿Hay alguna otra herramienta para detectar código no eliminado?

Karol Lewandowski
fuente
55
Las métricas de cobertura nunca han sido una buena forma de determinar si su código está bien probado, sino simplemente una forma de determinar lo que no se ha probado. Un buen desarrollador pensará en los diversos casos en su mente y diseñará pruebas para todos ellos, o al menos todo lo que ella considere importante.
kdgregory
3
Por supuesto, la alta cobertura condicional no significa que tengamos buenas pruebas, pero creo que es una GRAN ventaja saber qué rutas de ejecución están descubiertas y de eso se trata principalmente la pregunta. Sin cobertura condicional, es mucho más difícil detectar escenarios no probados. En cuanto a las rutas: [usuario: null], [usuario: notnull, user.name:null], [usuario: notnull, user.name:notnull]. ¿Qué me estoy perdiendo?
Karol Lewandowski
66
¿Cuál es el contrato de getName? Parece ser que si useres nulo, debería devolver "desconocido". Si userno es nulo y user.getName()es nulo, debería devolver "desconocido". Si userno es nulo y user.getName()no lo es, debería devolverlo. Por lo tanto, haría una prueba unitaria de esos tres casos porque de eso se trata el contrato getName. Parece que lo estás haciendo al revés. No desea ver las ramas y escribir las pruebas de acuerdo con ellas, desea escribir sus pruebas de acuerdo con su contrato y asegurarse de que el contrato se cumpla. Ahí es cuando tienes una buena cobertura.
Vincent Savard el
1
Una vez más, no digo que la cobertura demuestre que mi código está perfectamente probado, pero fue una herramienta EXTREMADAMENTE valiosa que me mostró lo que no he probado con seguridad. Creo que probar contratos es inseparable de probar rutas de ejecución (su ejemplo es excepcional ya que implica un mecanismo de lenguaje implícito). Si no ha probado la ruta, no ha probado completamente el contrato o el contrato no está completamente definido.
Karol Lewandowski
2
Voy a repetir mi punto anterior: ese es siempre el caso, a menos que se limite solo a las características básicas del lenguaje y nunca llame a ninguna función que no haya sido instrumentada. Lo que significa que no hay bibliotecas de terceros ni uso del SDK.
kdgregory

Respuestas:

4

¿Hay alguna herramienta que mida las ramas lógicas que se pueden crear en Java 8?

No estoy al tanto de ninguno. Intenté ejecutar el código que tienes a través de JaCoCo (también conocido como EclEmma) solo para estar seguro, pero muestra 0 ramas en la Optionalversión. No conozco ningún método para configurarlo para decir lo contrario. Si lo configuró para incluir también archivos JDK, teóricamente mostraría ramas Optional, pero creo que sería una tontería comenzar a verificar el código JDK. Solo tienes que asumir que es correcto.

Sin embargo, creo que el problema central es darse cuenta de que las ramas adicionales que tenía antes de Java 8 eran, en cierto sentido, ramas creadas artificialmente. Que ya no existan en Java 8 solo significa que ahora tiene la herramienta adecuada para el trabajo (en este caso Optional). En el código anterior a Java 8, tenía que escribir pruebas unitarias adicionales para poder confiar en que cada rama de código se comporta de manera aceptable, y esto se vuelve un poco más importante en secciones de código que no son triviales como User/ getNameejemplo.

En el código Java 8, en cambio, confía en el JDK de que el código funciona correctamente. Tal como está, debe tratar esa Optionallínea tal como lo tratan las herramientas de cobertura de código: 3 líneas con 0 ramas. Que haya otras líneas y ramas en el código a continuación es algo a lo que no ha prestado atención antes, pero ha existido cada vez que ha usado algo como un ArrayListo HashMap.

Shaz
fuente
2
"El hecho de que ya no existen en Java 8 ..." - No estoy de acuerdo con él, Java 8 es compatible con versiones anteriores y ify nullson todavía partes de la lengua ;-) Todavía es posible escribir código en forma de edad y pasar nullusuario o usuario con nullnombre. Sus pruebas solo deben probar que se cumple el contrato, independientemente de cómo se implemente el método. El punto es que no hay una herramienta que le diga si probó completamente el contrato.
Karol Lewandowski
1
@KarolLewandowski Creo que lo que dice Shaz es que si confías en cómo funcionan Optional(y los métodos relacionados), ya no tienes que probarlos. No de la misma manera que probaste un if-else: todo ifera un campo minado potencial. Optionaly modismos funcionales similares ya están codificados y se garantiza que no te harán tropezar, por lo que esencialmente hay una "rama" que desapareció.
Andres F.
1
@AndresF. No creo que Karol sugiera que hagamos la prueba Optional. Como dijo, lógicamente aún deberíamos probar que getName()maneja varias entradas posibles en la forma en que pretendemos, independientemente de su implementación. Es más difícil determinar esto sin herramientas de cobertura de código que ayuden de la forma en que sería anterior a JDK8.
Mike Partridge
1
@MikePartridge Sí, pero el punto es que esto no se hace a través de la cobertura de sucursal. Se necesita cobertura de rama cuando se escribe if-elseporque cada uno de esos constructos es completamente ad-hoc. Por el contrario, Optional, orElse, map, etc, están todos ya probado. Las ramas, en efecto, "desaparecen" cuando usas modismos más poderosos.
Andres F.