Модульное тестирование инжектированных объектов JSR-330

Я ранее использовал Spring DI, и одно из преимуществ, которые я ощущаю, заключается в том, что я могу тестировать свои классы bean-компонентов Spring без использования Spring (импорт для краткости опущен):

public class Foo {
    private String field;

    public void setField(String field) { this.field = field; }
    public String getField() { return field; }
} 

public class TestFoo {
    @Test
    public void test_field_is_set() {
       Foo foo = new Foo();
       foo.setField("Bar");
       assertEquals("Bar", foo.getField());
    }
}

Теперь я экспериментирую с JSR-330, что означает, что я не пишу сеттеры явно.

Я пока использую Hk2 исключительно из-за некоторых анекдотических вещей о том, что Джерси привязан к Hk2, и что затрудняет совместное использование с другими реализациями JSR-330.

public class Foo {
   @Inject 
   private String field;
}

Я наполовину ожидал, что произойдет какая-то магия, когда аннотация @Inject сделает доступным сеттер, но это не так:

Foo foo = new Foo();
foo.setField("Bar"); // method setField(String) is undefined for the type Foo
  • Как я могу (удобно) протестировать этот тип аннотированного класса без вызова фреймворка?
  • В противном случае, как я могу вызвать фреймворк переносимым способом (т.е. без тесной привязки моего тестового кода к Hk2, Guice и т. Д.)
  • В противном случае, что это за типичный чистый способ тестирования классов, аннотированных таким образом?

person slim    schedule 05.11.2013    source источник
comment
возможный дубликат Вставить в частный, пакет или общедоступный поле или предоставить сеттер?   -  person artbristol    schedule 05.11.2013
comment
Думаю, разница есть. Связанный вопрос: писать сеттеры или нет. Вопрос в том, как мне тестировать, если я не использую сеттеры.   -  person slim    schedule 05.11.2013
comment
Просто используйте инъекцию конструктора. Тогда эта проблема исчезнет.   -  person Vladimir Matveev    schedule 05.11.2013


Ответы (4)


Самый простой - сделать поля пакетными (а не частными), а затем в тесте установить их напрямую. (Это работает, если тест находится в одном пакете)

public class Foo {
   @Inject 
   String field;
}



Foo foo = new Foo();
foo.field = "bar";

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

person artbristol    schedule 07.11.2013

Упомянутый вами подход к внедрению полей на самом деле является типичным стилем Spring; многие программисты вообще не пишут сеттеры для частных вводимых полей. Контейнеры Spring (с @Autowired или @Inject) и JSR-330 обычно вводят поля, используя прямое отражение поля, а не сеттеры.

Из-за этого, если вы не хотите использовать какую-либо структуру DI, вы можете сами написать необходимый код отражения в своих модульных тестах, но это кажется излишним, чтобы избежать тестовой зависимости; в конце концов, смысл использования @Inject в том, что вы кодируете интерфейс и не избегаете использования JVM, чтобы избежать связи с ним.

Обычный подход к тестированию такого рода классов - установить тестовый контекст для любого контейнера, который вы предпочитаете, и запустить модульные тесты в этом контексте. Если вы используете Spring, вы должны поместить applicationContext-test.xml файл или TestConfig класс в свой src/test/ каталог (или эквивалент), а если вы используете Guice, вы должны написать модуль для подключения макетов или тестовых наборов данных.

person chrylis -cautiouslyoptimistic-    schedule 05.11.2013

Оказывается, фреймворки, полагающиеся на доступ к частным / защищенным полям, не так уж редки. Hibernate, JPA, несколько реализаций JSR-330, включая сам Spring, все это делают.

Пакет Spring-test предоставляет ReflectionTestUtils, содержащий статические методы для доступа к этим полям.

Используя это, можно проверить класс в вопросе следующим образом:

import static org.springframework.test.util.ReflectionTestUtils.*;

...

@Test
public void testUsingSpringReflectionTestUtils() {
    Foo foo = new Foo();
    setField(foo, "field", "Bar");
    assertEquals("Bar", foo.getField());
}

Чтобы это работало, вам нужны spring -test и spring-core в пути к тестовым классам, но это не добавляет зависимости от Spring для вашего производственного кода.

(Комментарии об альтернативных реализациях того же принципа приветствуются. Я не думаю, что стоит использовать собственную, какой бы простой она ни была, учитывая, что Spring имеет хорошую реализацию.)

person slim    schedule 05.11.2013

Попробуйте "иголку": http://needle.spree.de/overview

Needle - это структура DI-тестирования, которая только имитирует поведение контейнера, что делает модульные тесты очень простыми.

person Jan Galinski    schedule 05.11.2013