Pregunta Prueba de unidades de escritura para el parámetro anotado @Nonnull


Tengo un método como este:

 public void foo(@Nonnull String value) {...}

Me gustaría escribir una prueba de unidad para asegurarme foo() arroja un NPE cuando value es null pero no puedo, ya que el compilador se niega a compilar la prueba unitaria cuando el análisis de flujo de puntero nulo está habilitado en IDE.

¿Cómo puedo hacer que esta prueba compile (en Eclipse con "Habilitar análisis nulo basado en anotaciones" habilitado):

@Test(expected = NullPointerException.class)
public void test() {
     T inst = ...
     inst.foo(null);
}

Nota: En teoría, el puntero nulo estático del compilador debería evitar casos como ese. Pero no hay nada que impida que alguien escriba otro módulo con el análisis de flujo estático desactivado y llamando al método con null.

Caso común: gran proyecto antiguo desordenado sin análisis de flujo. Comienzo anotando algún módulo de utilidad. En ese caso, tendré pruebas de unidad existentes o nuevas que verifican cómo se comporta el código para todos los módulos que no lo hagas usar el análisis de flujo todavía.

Supongo que tengo que mover esas pruebas a un módulo sin marcar y moverlas a medida que extiendo el análisis de flujo. Eso funcionaría y encajaría bien en la filosofía, pero sería mucho trabajo manual.

Para decirlo de otra manera: no puedo escribir fácilmente una prueba que dice "éxito cuando el código no se compila" (tendría que poner fragmentos de código en los archivos, invocar el compilador de las pruebas unitarias, verificar errores en la salida. .. no es bonito). Entonces, ¿cómo puedo probar fácilmente que el código falla como debería cuando las personas que llaman ignoran @Nonnull?


5
2017-08-15 12:13


origen


Respuestas:


Ocultación null dentro de un método funciona el truco:

public void foo(@NonNull String bar) {
    Objects.requireNonNull(bar);
}

/** Trick the Java flow analysis to allow passing <code>null</code>
 *  for @Nonnull parameters. 
 */
@SuppressWarnings("null")
public static <T> T giveNull() {
    return null;
}

@Test(expected = NullPointerException.class)
public void testFoo() {
    foo(giveNull());
}

Lo anterior compila bien (y sí, doblemente verificado - cuando se usa foo(null) mi IDE me da un error de compilación, por lo que la "verificación nula" está habilitada).

En contraste con la solución dada a través de los comentarios, lo anterior tiene el agradable efecto secundario para trabajar para alguna tipo de tipo de parámetro (pero probablemente necesite Java8 para obtener la inferencia de tipo correcta siempre).

Y sí, la prueba pasa (como se describió anteriormente) y falla al comentar el Objects.requireNonNull() línea.


6
2017-08-15 14:53



¿Por qué no usar simplemente reflejo antiguo?

try {
    YourClass.getMethod("foo", String.class).invoke(someInstance, null);
    fail("Expected InvocationException with nested NPE");
} catch(InvocationException e) {
    if (e.getCause() instanceof NullPointerException) {
        return; // success
    }
    throw e; // let the test fail
}

Tenga en cuenta que esto puede romperse inesperadamente al refactorizar (cambie el nombre del método, cambie el orden de los parámetros del método, mueva el método al nuevo tipo).


2
2017-08-15 19:10



Aquí el diseño por contrato llega a la imagen. No puede proporcionar un parámetro de valor nulo a un método anotado con un argumento no nulo.


0
2017-08-15 13:04



Puede usar un campo que inicialice y luego configure para null en un método de configuración:

private String nullValue = ""; // set to null in clearNullValue()
@Before
public void clearNullValue() {
    nullValue = null;
}

@Test(expected = NullPointerException.class)
public void test() {
     T inst = ...
     inst.foo(nullValue);
}

Como en la respuesta de GhostCat, el compilador no puede saber si y cuando clearNullValue() se llama y tiene que asumir que el campo no es null.


0
2017-08-16 08:26