单元测试环境中SpringBeans的重新定义

单元测试环境中SpringBeans的重新定义,spring,unit-testing,spring-test,Spring,Unit Testing,Spring Test,我们将Spring用于我的应用程序,将Spring测试框架用于单元测试。但我们有一个小问题:应用程序代码从类路径中的位置列表(XML文件)加载Spring应用程序上下文。但是当我们运行单元测试时,我们希望一些Springbean是模拟的,而不是成熟的实现类。此外,对于一些单元测试,我们希望一些bean成为mock,而对于其他单元测试,我们希望其他bean成为mock,因为我们正在测试应用程序的不同层 所有这些都意味着我想重新定义应用程序上下文的特定bean,并在需要时刷新上下文。在执行此操作时,

我们将Spring用于我的应用程序,将Spring测试框架用于单元测试。但我们有一个小问题:应用程序代码从类路径中的位置列表(XML文件)加载Spring应用程序上下文。但是当我们运行单元测试时,我们希望一些Springbean是模拟的,而不是成熟的实现类。此外,对于一些单元测试,我们希望一些bean成为mock,而对于其他单元测试,我们希望其他bean成为mock,因为我们正在测试应用程序的不同层

所有这些都意味着我想重新定义应用程序上下文的特定bean,并在需要时刷新上下文。在执行此操作时,我只想重新定义位于一个(或多个)原始XMLBeans定义文件中的一小部分Bean。我找不到一个简单的方法来做这件事。人们一直认为Spring是一个单元测试友好的框架,所以我肯定错过了一些东西

你知道怎么做吗


谢谢

简单。您可以为单元测试使用自定义应用程序上下文,或者根本不使用自定义应用程序上下文,而是手动创建和注入bean


我觉得你的测试可能有点太宽了。单元测试是关于测试,嗯,单元。Springbean是一个很好的单元示例。您不应该需要整个应用程序上下文。我发现,如果您的单元测试非常高,需要数百个bean、数据库连接等,那么您的单元测试将非常脆弱,在下一次更改时将被破坏,很难维护,并且不会增加很多价值。

也许您可以为您的bean使用限定符?您可以在单独的应用程序上下文中重新定义要模拟的bean,并用限定符“test”标记它们。在单元测试中,连接bean时,始终指定限定符“test”以使用实体模型。

您可以在测试应用程序上下文中使用该功能加载到prod bean中,并覆盖所需的bean。例如,我的产品数据源通常是通过JNDI查找获取的,但当我进行测试时,我使用了DriverManager数据源,因此我不必启动应用服务器进行测试。

您也可以编写单元测试,以完全不需要任何查找:

@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 bean(用于测试和主应用程序),在另一个配置文件中有by dataSource bean(一个可能使用DriverManager数据源访问内存中的db,另一个可能使用JNDI查找)


但是,一定要注意警告;-)

我将为springbean.xml的位置提出一个定制的
TestClass
和一些简单的规则:

@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
,它们将覆盖“正常”位置中的所有“real”
bean.xml
文件-您的正常位置可能不同


但是…我永远不会混合使用模拟bean和非模拟bean,因为当应用程序变老时很难跟踪问题。

spring被描述为测试友好型的原因之一是,在单元测试中只使用新的或模拟的东西可能很容易

或者,我们已经成功地使用了以下设置,我认为它非常接近您想要的设置,我强烈推荐它:

对于在不同上下文中需要不同实现的所有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>
请注意,此设置不允许存根/活动数据的交替组合。我们试过这个,我想结果是一团糟,我不推荐任何人;)我们要么将全套存根连接起来,要么提供全套实时服务

在测试gui时,我们主要使用自动连接的存根依赖关系,这些依赖关系通常非常重要。在更干净的代码区域,我们使用更常规的单元测试

在我们的系统中,我们有以下用于组件扫描的xml文件:

  • 用于常规web生产
  • 仅用于使用存根启动web
  • 用于集成测试(在junit中)
  • 用于单元测试(在junit中)
  • 用于selenium web测试(在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附近的区域。

我想做同样的事情,我们发现它很重要

我们目前使用的机制是相当手动的,但它可以工作

比如说,您希望模拟Y类型的bean,我们所做的是每个具有依赖关系的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。执行此操作时,当bean id相同时,子工厂将覆盖父工厂中的bean

现在,在我的测试中,如果我能以编程方式创建一个工厂,那么
 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);
        }
    }
junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject);
@Mock
SomeClass mockedSomeClass

@InjectMock
ClassUsingSomeClass service

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