Юнит-тестирование закачиваемых объектов 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 и т. Д.)
- В противном случае, каков типичный, чистый способ проверки классов, аннотированных таким образом?
4 ответа
Оказывается, что фреймворки, основанные на частном / защищенном доступе к полям, не так уж редки Hibernate, JPA, несколько реализаций JSR-330, включая сам Spring, делают это.
Spring-тестовый пакет Spring предоставляет класс 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 имеет хорошую реализацию.)
Проще всего сделать поля package-private (вместо private), а затем в тесте установить их напрямую. (Это работает, если тест находится в той же упаковке)
public class Foo {
@Inject
String field;
}
Foo foo = new Foo();
foo.field = "bar";
Преимущество в том, что вы избегаете рефлексии, поэтому это безопасно для рефакторинга.
Упомянутый вами подход к полевым инжекциям на самом деле типичный стиль Spring; многие программисты вообще не пишут сеттеры для закрытых вставленных полей. Весна (с @Autowired
или же @Inject
Контейнеры) и JSR-330 обычно вводят поля, используя прямое отражение поля, а не сеттеры.
Из-за этого, если вы не хотите использовать какую-либо инфраструктуру DI, вы можете сами добавить необходимый код отражения в свои модульные тесты, но это кажется излишним, просто чтобы избежать зависимости теста; в конце концов, смысл использования @Inject
в том, что вы кодируете интерфейс, и вы не избегаете использования JVM, чтобы избежать соединения с ним.
Обычный подход к тестированию такого класса заключается в том, чтобы установить контекст теста для любого контейнера, который вы предпочитаете, и запустить модульные тесты в этом контексте. Если вы используете Spring, вы бы положили applicationContext-test.xml
файл или TestConfig
класс в вашем src/test/
каталог (или эквивалент), и если вы используете Guice, вы бы написали модуль для подключения макетов или тестовых наборов данных.
Дайте "иглу" попробовать: http://needle.spree.de/overview
needle - это DI-test-framework, который имитирует только поведение контейнера, делая модульные тесты очень простыми.