2009-02-19 3 views
49

Мы используем Spring для моих приложений и Spring Testing для модульных тестов. У нас есть небольшая проблема: код приложения загружает контекст приложения Spring из списка местоположений (xml-файлов) в пути к классам. Но когда мы запускаем наши модульные тесты, мы хотим, чтобы некоторые из Spring beans были mocks вместо полноценных классов реализации. Более того, для некоторых модульных тестов мы хотим, чтобы некоторые бобы стали издевательствами, в то время как для других модульных тестов мы хотим, чтобы другие бобы становились насмешками, поскольку мы тестируем разные уровни приложения.Переопределение весенних фасолей в единичной тестовой среде

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

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

Спасибо.

+8

Весна делает модульное тестирование мертвым простым. Это та часть, которую вам не хватает - вы проводите интеграционный тест, а не Unit Test. В истинном модульном тесте любые зависимые компоненты должны быть mocks, потому что вы проверяете только единицу, а не всю систему. – bpapa

+0

Как выполнить * единичный тест * весеннее приложение: http://confessionsofanagilecoach.blogspot.com/2016/05/unit-testing-spring-applications.html –

ответ

18

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

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

3

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

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

+0

Вы правы, мы используем довольно системное тестирование, а не модульное тестирование , мы фактически проверяем поток от API к БД на нашем сервере приложений. Я понимаю, что блок-тест является лучшим способом, но в настоящий момент это невозможно. – Stas

0

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

2

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

4

Вы также можете написать модульные тесты не требуют какого-либо Lookups вообще:

@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 у меня может быть мой компонент sessionFactory в одном файле конфигурации (который будет использоваться как в тестах, так и в главном приложении), а с помощью компонента dataSource в другом файле конфигурации (можно использовать DriverManagerDataSource на встроенную память, другой может использовать JNDI-поиск).

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

+0

это привело бы ко многим дублирующимся bean.xml для тестов, для теста с макетами я бы скорее предложил easyMock или аналогичные макетные фреймворки –

+0

Yep - некоторые из наших тестов использовали EasyMock вместо этого подхода. IMHO EasyMock имеет тенденцию сиять для реальных модульных испытаний. Более тесные «интеграционные» тесты выигрывают от подхода ContextConfiguration. – toolkit

16

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

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

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

Реализовать следующий набор аннотаций

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

Обратите внимание, что эта установка делает не позволяют иметь чередующиеся комбинации окурка/данных в реальном времени. Мы попробовали это, и я думаю, что это привело к беспорядку, который я бы никому не рекомендовал;) Мы либо проводим полный набор заглушек, либо полный набор живых сервисов.

Мы в основном используем зависящие от автозапуска зависимости при тестировании gui около того, где зависимости обычно довольно существенны. В более чистых областях кода мы используем более регулярное модульное тестирование.

В нашей системе мы имеем следующие XML-файлы для компонентов сканирования:

  • для регулярного веб-производства
  • для запуска веб-заглушками только
  • для интеграции тестов (в JUnit)
  • для модульных тестов (в JUnit)
  • для веб-тестов селена (в JUnit)

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

Out интеграции тестов работать с полной живой установкой, и один или два раза я решил получить действительно прагматичен и хочет иметь 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); 
    } 


    } 
} 

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

+0

Просто любопытно, разве это не приводит к некоторому адскому поведению относительно индивидуального @ContextConfiguration за Testclass? –

+0

Нет, у нас всего 5 системных @ConfigurationContxts * полностью * в большом приложении. Я отредактировал его, чтобы сделать это более ясным. – krosenvold

+0

Спасибо за этот ответ, я применил его с успехом. Следует отметить, что этот способ очень гибкий, поскольку вы можете использовать несколько контекстных тегов: компонентный сканер (например, один на модуль или на пакет). Кроме того, это может помочь установить «use-default-filters =« false ». –

0

Я хочу сделать то же самое, и мы находим это необходимым.

Текущий механизм, который мы используем, довольно ручной, но он работает.

Скажите, например, вы хотите издеваться над 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-компонента одинаковы.

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

6

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

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

Здесь:

junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject); 
+3

Или, если вы используете spring: 'org.springframework.test.util.ReflectionTestUtils.setField (testSubject," fieldName ", mockObject);' – DrUseful

+2

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

+0

Предполагается, что используемый класс не получает доступ к используемому классу в его конструкторе или init-методе. В этом случае замена поля bean с помощью отражения произойдет слишком поздно. –

1

Я не имею очки репутации навалить ответ duffymo, но я просто хотел, чтобы куранты и сказать его был «правильный» ответ для меня.

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

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

7

Смотреть это tutorial with @InjectedMock annotation

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

@Mock 
SomeClass mockedSomeClass 

@InjectMock 
ClassUsingSomeClass service 

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

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

0

spring-reinject предназначен для замены бобы с помощью издевательств.

1

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