Написание модульного теста для аннотированного параметра @Nonnull

У меня есть такой метод:

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

Я хотел бы написать модульный тест, чтобы убедиться, foo() бросает NPE, когда value является null но я не могу, так как компилятор отказывается компилировать модульный тест, когда статический анализ потока нулевого указателя включен в IDE.

Как мне сделать эту тестовую компиляцию (в Eclipse с включенным "Включить аннулирование на основе аннотаций"):

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

Примечание: теоретически статический нулевой указатель компилятора должен предотвращать подобные случаи. Но ничто не мешает кому-то написать другой модуль с отключенным статическим анализом потока и вызвать метод с null,

Типичный случай: старый грязный старый проект без анализа потока. Я начну с аннотирования некоторого служебного модуля. В этом случае у меня будут существующие или новые модульные тесты, которые проверяют поведение кода для всех модулей, которые еще не используют анализ потока.

Я предполагаю, что мне нужно перенести эти тесты в непроверенный модуль и перемещать их по мере распространения анализа потока. Это будет работать и хорошо вписываться в философию, но это будет много ручной работы.

Другими словами: я не могу легко написать тест, который говорит: "Успех, когда код не компилируется" (мне пришлось бы помещать куски кода в файлы, вызывать компилятор из модульных тестов, проверять вывод на наличие ошибок... не красиво). Итак, как я могу легко проверить, что код не работает, как это должно быть, когда вызывающие игнорируют @Nonnull?

5 ответов

Решение

Прячется null в методе делает трюк:

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());
}

Выше скомпилируется нормально (и да, дважды проверил - при использовании foo(null) моя IDE дает мне ошибку компиляции - так что "проверка нуля" включена).

В отличие от решения, предоставленного в комментариях, вышеприведенное имеет приятный побочный эффект для работы с любым типом параметра (но, вероятно, может потребоваться Java8, чтобы всегда делать правильный вывод типа).

И да, тест проходит (как написано выше) и не проходит при комментировании Objects.requireNonNull() линия.

Почему бы просто не использовать простое старое отражение?

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
}

Обратите внимание, что это может неожиданно прерваться при рефакторинге (вы переименуете метод, измените порядок параметров метода, переместите метод на новый тип).

Используя assertThrows из утверждений Юпитера, я смог проверить это:

      public MethodName(@NonNull final param1 dao) {....

assertThrows(IllegalArgumentException.class, () -> new MethodName(null));

Здесь дизайн по контракту приходит к картине. Вы не можете предоставить нулевое значение параметра методу, аннотированному аргументом notNull.

Вы можете использовать поле, которое вы инициализируете, а затем установите null в методе настройки:

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);
}

Как и в ответе GhostCat, компилятор не может знать, когда и когда clearNullValue() называется и должен предполагать, что поле не null,

Другие вопросы по тегам