【问题标题】:unit testing in an entangled spring context纠缠弹簧上下文中的单元测试
【发布时间】:2013-04-07 10:38:21
【问题描述】:

假设一个遗留应用程序有 1500 个 spring.xmls。我想为服务编写单元测试。我深陷依赖地狱。我只好按原样接受应用,没有出路。

所以我们使用 spring-3.something 和 mockito-1.9,我想要测试服务的好方法。较新的代码大量使用@Autowired 注解。

间接地,该服务使用了 ~25 个我真正想在测试中使用的助手(工厂方法等),以及 ~25 个我对此测试不感兴趣的对象。

我目前尝试以上述方式设置上下文,但我对@Mock、@InjectMocks、@Autowired 的影响感到困惑。

我的测试如下。我需要帮助才能正确设置。

问题:

  • @InjectMocks 的实际效果是什么?
  • 我如何才能(技术上)决定哪些自动装配的 bean 真正被使用,哪些被模拟替换?
  • 我知道,我在滥用模拟来获取假货。有没有更简单的方法在单线中获取假货?
  • *请注意,我想了解这一点,因为我有大量此类服务... *

这是我的示例:

@ContextConfiguration(locations = {
    "classpath:/some/path/MainTestConfig.spring.xml"
})
@RunWith(SpringJUnit4ClassRunner.class)
public class SampleTest {

    // ***            Uninteresting Dependencies to be mocked        *** //
    @Mock Mock1 mock1;
    @Mock Mock2 mock2;

    /** Service under test */
    @Autowired
    SomeService service;

    // ***            Tightly coupled helpers to be used              *** //
    @Autowired Helper1 helper1
    @Autowired Helper2 helpr2

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(SampleTest.class);
    }

    @Test
    public void testSample() {

        // prepare dummy context
        SomeContext context = new Context();

        // define expected result
        int expectedValue = 42;

        //execute method under test, record result
        Result actualResult = service.execute(context);

        //make assertions on result
        assertTrue(actualResult.getSomething()==expectedValue);
    }

}

【问题讨论】:

  • 我不明白为什么你会有 mock1mock2 在你的测试中似乎不相关,因为它们无关紧要。加上似乎没有使用助手,在测试中得到验证。如果一切都由弹簧连接,你为什么要它。我相信您实际上应该模拟紧密耦合的助手,并验证交互。此外,最终您可以将此测试编写为大型功能/特性测试,即不是单元测试也不是集成测试,因此它将被放置在其他一些包中以显示您的同事的差异。
  • 顺便问一下,您是否尝试过 spring 3 中可用的注释配置?如果您只需要注入一些依赖项,它可能会很有用,并且这也适用于模拟(不需要 springockito,如果添加另一个 maven 依赖项是一个问题)。

标签: spring junit mockito springmockito


【解决方案1】:

我想将此作为单独的答案发布,因为这应该更好地解决您关于@Mock、@Autowired 和@InjectMocks 的实际问题。

@Mock:这标记了在调用MockitoAnnotations.initMocks(this) 时应创建为模拟(使用mock(MyClass.class))的字段。

@Autowired:用实现类/接口的bean标记应该由Spring分配的字段。

@InjectMocks:在调用MockitoAnnoations.initMocks(this) 时标记应由 Mockito 创建的字段。它创建该类的一个实例并将@Mock-annotated 字段注入到该实例中。 (请参阅 cmets 中对此声明的更正)。

分析:

@InjectMocks 与 Spring 上下文和 @Autowired 不兼容,因为 InjectMock 创建了一个它不使用 Spring 实例的类的新实例。

要执行您要查找的操作,您需要使用Springockito。 (延迟更新)Springockito 将允许您将模拟注入到您的 Spring 上下文中,从而将这些模拟用作Autowired 候选对象。它允许模拟和间谍。在测试中的同一字段上使用 ReplaceWithMockAutowired 是一般做法(如 wiki 上的示例所示)。

【讨论】:

  • 此语句不正确:“...InjectMock 创建了一个不使用 Spring 实例的类的新实例”。实际的 Mockito 行为是,如果在 mockito 注入时字段已经有一个实例,它将永远不会创建实例。虽然我承认文档并不清楚这一点:InjectMocks Javadoc。所以可以在 Spring 管理的 bean 中注入 mock,但是我不推荐它,因为它在测试设置中增加了很多复杂性。
  • @Brice 你是说如果你在测试中将@Autowired@InjectMocks(需要自动装配来获取Spring bean)放在同一个字段上,Spring bean 将被使用并且它将通过将测试中的模拟注入其中来更新?
  • 但是请注意,spring 管理的 bean 将在 spring 上下文中使用他一生的 mock 进行更新,所以我不推荐它。使用脏上下文技巧可以工作,但它的速度要慢得多,尤其是对于巨大的弹簧上下文,因为 Spring 将为每个测试方法加载上下文。
【解决方案2】:

第一个问题,你需要加载弹簧上下文来做这个测试吗?通常,当我有 Spring 项目时,每个类都有一个不加载上下文文件的 UNIT 测试。我将有一个不同的 CONTEXT 测试来加载上下文文件并验证它是否正确加载。如果您正在进行真正的单元测试,我建议您不要加载上下文。如果被测类使用@Autowired 分配依赖项(因此没有设置器)使用Spring 的ReflectionTestUtils 分配这些字段。

【讨论】:

  • 好吧,我正在测试的单元是服务+它的助手。为此,我需要设置 some 上下文,该上下文由不做任何事情的助手和假人组成。手动设置这个上下文对于 50 个类是不可行的,所以我想在可能的情况下重用现有的 spring 配置,并在可行的情况下注入模拟。看来我还没有得到“InjectMocks”......
  • 顺便说一句:什么是“真正的”单元测试?你认为什么是单位?我怀疑用“类”或“方法”替换单元。我的单位是服务+帮手。超出该边界的所有内容都将被嘲笑/伪造。
  • 一个“真正的”单元测试是你的测试只涉及一个类(单元)。所有其他类/依赖项都被模拟。除了被测类之外,我允许的唯一具体类是数据传输对象 (DTO),它们只是数据持有者(字段、getter、setter)。
  • 如果您正在测试“服务+它的助手”,我不会认为这是一个真正的单元测试。并不是说这是一个糟糕的测试,只是名称不同。在我们的项目中,我们称之为“功能测试”(完全是任意名称,但与“单元测试”不同)。这些设置通常更具挑战性,有时确实使用有限的 Spring 上下文。但是,他们应该有更少的测试,而不是每个类都有所有的“单元”测试。这些测试的目的应该是确保类正确交互。
  • 嗯,我的测试教育似乎使用了过时的命名:在 软件测试的艺术,Myers,1979! 中定义是:模块测试(或单元测试)是测试程序中各个子程序、子例程或过程的过程。 这是我的理解,但它似乎与当前(基于 OO 的)对 ( j) 单元测试。事实上,我正在寻找一种在更大级别(功能,但尚未完全集成的应用程序)上进行测试的方法,而不是在类或方法级别上进行过于细化的测试(正如我所说,我生活在遗留环境中......)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多