Переопределение бобов Spring в среде модульных тестов - PullRequest
50 голосов
/ 19 февраля 2009

мы используем Spring для моих приложений и Spring Testing Framework для модульных тестов. Однако у нас есть небольшая проблема: код приложения загружает контекст приложения Spring из списка местоположений (XML-файлов) в пути к классам. Но когда мы запускаем наши модульные тесты, мы хотим, чтобы некоторые компоненты Spring были фиктивными, а не полноценными классами реализации. Более того, для некоторых модульных тестов мы хотим, чтобы некоторые bean-компоненты становились фиктивными, в то время как для других модульных тестов мы хотим, чтобы другие bean-компоненты становились фиктивными, поскольку мы тестируем разные уровни приложения.

Все это означает, что я хочу переопределить определенные bean-компоненты контекста приложения и обновить контекст при желании. При этом я хочу переопределить только небольшую часть bean-компонентов, расположенных в одном (или нескольких) оригинальном файле определения bean-компонентов xml. Я не могу найти легкий способ сделать это. Всегда считалось, что Spring - дружественная среда для модульного тестирования, поэтому я должен что-то здесь упустить.

У вас есть идеи, как это сделать?

Спасибо.

Ответы [ 13 ]

18 голосов
/ 19 февраля 2009

Я бы предложил собственный 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 в "обычных" местах - ваши обычные местоположения могут отличаться

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

16 голосов
/ 20 февраля 2009

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

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

Для всех 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.

7 голосов
/ 01 ноября 2013

См. учебник с аннотацией @InjectedMock

Это сэкономило мне много времени. Вы просто используете

@Mock
SomeClass mockedSomeClass

@InjectMock
ClassUsingSomeClass service

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}

и все ваши проблемы решены. Mockito заменит пружинный впрыск зависимостей на макет. Я просто использовал его сам, и он прекрасно работает.

6 голосов
/ 16 июня 2009

Здесь перечислены некоторые очень сложные и мощные решения.

Но есть FAR, FAR более простой способ выполнить то, о чем просил Стас, который не включает изменение ничего, кроме одной строки кода в методе теста. Он работает как для модульных тестов, так и для интеграционных тестов Spring, для автоматических зависимостей, закрытых и защищенных полей.

Вот оно:

junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject);
4 голосов
/ 19 февраля 2009

Вы также можете написать свои модульные тесты, чтобы вообще не требовать поиска:

@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).

Но, обязательно примите во внимание предупреждение @ cletus ; -)

3 голосов
/ 19 февраля 2009

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

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

2 голосов
/ 19 февраля 2009

Вы можете использовать функцию import в контексте вашего тестового приложения, чтобы загружать компоненты Prod и переопределять те, которые вы хотите. Например, мой источник данных prod обычно получают с помощью поиска JNDI, но при тестировании я использую источник данных DriverManager, поэтому мне не нужно запускать сервер приложений для тестирования.

1 голос
/ 05 сентября 2016

Вам не нужно использовать какие-либо тестовые контексты (не имеет значения на основе XML или Java). Начиная с весенней загрузки 1.4 появилась новая аннотация @MockBean, в которой появилась встроенная поддержка насмешек и шпионажа Spring Beans.

1 голос
/ 25 июля 2009

У меня нет репутационных баллов, чтобы сложить ответ Даффимо, но я просто хотел вмешаться и сказать, что он был для меня «правильным» ответом.

Создание экземпляра FileSystemXmlApplicationContext в настройках модульного теста с помощью пользовательского applicationContext.xml. В этом пользовательском xml вверху сделайте как указано в duffymo. Затем объявите фиктивные бины, источники данных не-JNDI и т. Д., Которые переопределят идентификаторы, объявленные в импорте.

Работал для меня как мечта.

0 голосов
/ 30 января 2015

Начиная с ОП это произошло: Springockito

...