Переопределение бобов Spring в среде модульных тестов
Мы используем Spring для моих приложений и Spring Testing Framework для модульных тестов. Однако у нас есть небольшая проблема: код приложения загружает контекст приложения Spring из списка местоположений (XML-файлов) в пути к классам. Но когда мы запускаем наши модульные тесты, мы хотим, чтобы некоторые компоненты Spring были фиктивными, а не полноценными классами реализации. Более того, для некоторых модульных тестов мы хотим, чтобы некоторые bean-компоненты становились фиктивными, в то время как для других модульных тестов мы хотим, чтобы другие bean-компоненты становились фиктивными, так как мы тестируем разные уровни приложения.
Все это означает, что я хочу переопределить определенные bean-компоненты контекста приложения и обновить контекст при желании. При этом я хочу переопределить только небольшую часть bean-компонентов, расположенных в одном (или нескольких) оригинальном файле определения bean-компонентов xml. Я не могу найти легкий способ сделать это. Всегда считалось, что Spring - дружественная среда для модульного тестирования, поэтому я должен что-то здесь упустить.
У вас есть идеи, как это сделать?
Благодарю.
13 ответов
Я хотел бы предложить пользовательский TestClass и несколько простых правил для расположения весны bean.xml
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath*:spring/*.xml",
"classpath*:spring/persistence/*.xml",
"classpath*:spring/mock/*.xml"})
@Transactional
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DirtiesContextTestExecutionListener.class})
public abstract class AbstractHibernateTests implements ApplicationContextAware
{
/**
* Logger for Subclasses.
*/
protected final Logger LOG = LoggerFactory.getLogger(getClass());
/**
* The {@link ApplicationContext} that was injected into this test instance
* via {@link #setApplicationContext(ApplicationContext)}.
*/
protected ApplicationContext applicationContext;
/**
* Set the {@link ApplicationContext} to be used by this test instance,
* provided via {@link ApplicationContextAware} semantics.
*/
@Override
public final void setApplicationContext(
final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
если в указанном месте есть mock-bean.xml, они переопределят все "настоящие" bean.xml в "обычных" местах - ваши обычные места могут отличаться
но... я бы никогда не смешивал фиктивные и не фиктивные бины, трудно отследить проблемы, когда приложение стареет.
Одна из причин, по которой пружина описана как удобная для тестирования, заключается в том, что в модульном тесте может быть легко просто ввести новый материал или сделать имитацию.
С другой стороны, мы с большим успехом использовали следующую настройку, и я думаю, что она довольно близка к тому, что вы хотите, я настоятельно рекомендую:
Для всех bean-компонентов, которые нуждаются в разных реализациях в разных контекстах, переключитесь на разводку на основе аннотаций. Вы можете оставить остальных как есть.
Реализуйте следующий набор аннотаций
<context:component-scan base-package="com.foobar">
<context:include-filter type="annotation" expression="com.foobar.annotations.StubRepository"/>
<context:include-filter type="annotation" expression="com.foobar.annotations.TestScopedComponent"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
Затем вы аннотируете свои живые реализации с помощью @Repository, ваши реализации- заглушки - с помощью @StubRepository, а любой код, который должен присутствовать в модульном тесте, ТОЛЬКО с @TestScopedComponent. Вам может понадобиться еще пара аннотаций, но это отличное начало.
Если у вас много spring.xml, вам, вероятно, потребуется создать несколько новых весенних xml-файлов, которые в основном содержат только определения компонентного сканирования. Обычно вы просто добавляете эти файлы в свой обычный список @ContextConfiguration. Причина этого в том, что вы часто сталкиваетесь с различными конфигурациями сканирования контекста (поверьте мне, вы сделаете как минимум еще 1 аннотацию, если будете проводить веб-тесты, то есть для 4 соответствующих комбинаций)
Тогда вы в основном используете
@ContextConfiguration(locations = { "classpath:/path/to/root-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
Обратите внимание, что эта настройка не позволяет вам иметь чередующиеся комбинации данных заглушки / живых данных. Мы попробовали это, и я думаю, что это привело к путанице, которую я бы никому не рекомендовал;) Мы либо подключили полный набор заглушек, либо полный набор живых сервисов.
Мы используем в основном заглушки с автоматическим подключением при тестировании графического интерфейса рядом с вещами, где зависимости обычно довольно существенны. В более чистых областях кода мы используем более регулярное юнит-тестирование.
В нашей системе у нас есть следующие xml-файлы для компонентного сканирования:
- для регулярного веб-производства
- для запуска веб только с заглушками
- для интеграционных тестов (в junit)
- для юнит-тестов (в юнитах)
- для веб-тестов селена (в junit)
Это означает, что у нас есть 5 различных системных настроек, с помощью которых мы можем запустить приложение. Поскольку мы используем только аннотации, Spring достаточно быстр, чтобы автоматически связывать даже те модульные тесты, которые мы хотим подключить. Я знаю, что это нетрадиционно, но это действительно здорово.
Наши интеграционные тесты выполняются с полной настройкой в режиме реального времени, и один или два раза я решил стать по- настоящему прагматичным и хочу иметь 5 живых вайрингов и один макет:
public class HybridTest {
@Autowired
MyTestSubject myTestSubject;
@Test
public void testWith5LiveServicesAndOneMock(){
MyServiceLive service = myTestSubject.getMyService();
try {
MyService mock = EasyMock.create(...)
myTestSubject.setMyService( mock);
.. do funky test with lots of live but one mock object
} finally {
myTestSubject.setMyService( service);
}
}
}
Я знаю, что пуристы-испытатели будут за мной повсюду. Но иногда это просто очень прагматичное решение, которое оказывается очень элегантным, когда альтернатива будет действительно очень уродливой. Опять же, это обычно в тех близких к GUI районах.
Смотрите это руководство с аннотацией @InjectedMock
Это сэкономило мне много времени. Вы просто используете
@Mock
SomeClass mockedSomeClass
@InjectMock
ClassUsingSomeClass service
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
и все твои проблемы решены. Mockito заменит пружинный впрыск зависимостей на макет. Я просто использовал его сам, и он прекрасно работает.
Здесь перечислены некоторые очень сложные и мощные решения.
Но есть FAR, FAR более простой способ выполнить то, о чем просил Стас, который не включает в себя изменение чего-либо, кроме одной строки кода в тестовом методе. Он работает как для модульных тестов, так и для тестов интеграции Spring, для автоматических зависимостей, закрытых и защищенных полей.
Вот:
junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject);
Вы также можете написать свои модульные тесты, чтобы вообще не требовать поиска:
@ContextConfiguration(locations = { "classpath:/path/to/test-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class MyBeanTest {
@Autowired
private MyBean myBean; // the component under test
@Test
public void testMyBean() {
...
}
}
Это дает простой способ смешивать и сопоставлять реальные конфигурационные файлы с тестовыми конфигурационными файлами.
Например, при использовании hibernate у меня может быть bean-компонент sessionFactory в одном файле конфигурации (который будет использоваться как в тестах, так и в главном приложении), а bean-компонентом dataSource - в другом файле конфигурации (можно использовать DriverManagerDataSource для память базы данных, другой может использовать поиск JNDI).
Легко. Вы используете пользовательский контекст приложения для своих модульных тестов. Или вы не используете его вообще, и вы вручную создаете и внедряете ваши бины.
Мне кажется, что ваше тестирование может быть слишком широким. Юнит тестирование - это тестирование, ну, юнитов. Spring bean - довольно хороший пример юнита. Вам не нужен весь контекст приложения для этого. Я обнаружил, что если ваш модульный тест настолько высок, что вам нужны сотни bean-компонентов, соединений с базой данных и т. Д., То у вас будет действительно хрупкий модульный тест, который сломается при следующем изменении, его будет сложно поддерживать и на самом деле нет ". т добавив много ценности.
Вы можете использовать функцию импорта в контексте вашего тестового приложения, чтобы загрузить компоненты Prod и переопределить те, которые вы хотите. Например, мой источник данных prod обычно получается с помощью поиска JNDI, но при тестировании я использую источник данных DriverManager, поэтому мне не нужно запускать сервер приложений для тестирования.
Вам не нужно использовать какие-либо тестовые контексты (не имеет значения на основе XML или Java). Начиная с весенней загрузки 1.4 появилась новая аннотация @MockBean
который представил встроенную поддержку для насмешек и шпионажа из Spring Beans.
У меня нет очков репутации, чтобы навалить ответ Даффимо, но я просто хотел присоединиться и сказать, что он был "правильным" ответом для меня.
Создайте файл FileSystemXmlApplicationContext в настройках модульного теста с помощью пользовательского applicationContext.xml. В этом пользовательском xml вверху сделайте как указано в duffymo. Затем объявите фиктивные бины, источники данных не-JNDI и т. Д., Которые переопределят идентификаторы, объявленные в импорте.
Работал как мечта для меня.
Возможно, вы могли бы использовать квалификаторы для ваших бобов? Вы должны переопределить bean-компоненты, которые хотите макетировать, в отдельном контексте приложения и пометить их квалификатором "test". В ваших модульных тестах при подключении бобов всегда указывайте квалификатор "test" для использования макетов.
Я хочу сделать то же самое, и мы находим это необходимым.
Текущий механизм, который мы используем, довольно ручной, но он работает.
Скажем, например, вы хотите смоделировать bean-компонент типа Y. То, что мы делаем, - это каждый bean-компонент, имеющий такую зависимость, которую мы создаем для реализации интерфейса - "IHasY". Этот интерфейс
interface IHasY {
public void setY(Y y);
}
Затем в нашем тесте мы вызываем метод util...
public static void insertMock(Y y) {
Map invokers = BeanFactory.getInstance().getFactory("core").getBeansOfType(IHasY.class);
for (Iterator iterator = invokers.values().iterator(); iterator.hasNext();) {
IHasY invoker = (IHasY) iterator.next();
invoker.setY(y);
}
}
Я не хочу создавать целый XML-файл просто для внедрения этой новой зависимости, и именно поэтому мне это нравится.
Если вы готовы создать конфигурационный файл xml, тогда вам нужно будет создать новую фабрику с фиктивными компонентами и сделать фабрику по умолчанию родителем этой фабрики. Убедитесь, что вы загрузили все ваши бины с новой дочерней фабрики. При этом суб-фабрика переопределит bean-компоненты в родительской фабрике, когда идентификаторы bean-идентификаторов совпадают.
Теперь, если в моем тесте, если бы я мог программно создать фабрику, это было бы здорово. Необходимость использовать XML слишком громоздка. Я ищу, чтобы создать эту дочернюю фабрику с кодом. Тогда каждый тест может настроить свой завод так, как он хочет. Нет причины, по которой такая фабрика не будет работать.