Junit/Mockito:选择使用模拟或集成测试运行测试

Posted

技术标签:

【中文标题】Junit/Mockito:选择使用模拟或集成测试运行测试【英文标题】:Junit/Mockito: choosing to run test with mocks or integration tests 【发布时间】:2011-07-05 06:15:58 【问题描述】:

我正在学习 Mockito。在开始使用模拟对象之前,我有一些单元测试更像是集成测试,所以我有一个带有 setUpBeforeClass() 的测试类,如下所示:

@BeforeClass
public static void setUpBeforeClass() throws Exception 
    instance = new UserDataAccess();
    instance.setDb(new MyDb());

现在与 mock Object 很相似,但设置稍微复杂一些:

@BeforeClass
public static void setupBeforeClass throws Exception 
    instance = new UserDataAccess();
    MyDb myDb = mock(MyDb.class);
    when(...).thenReturn(...);
    ...
    instance.setDb(myDb);

我还有一个测试套件,用于在运行测试之前以众所周知的状态加载数据库,这是通过套件调用的第一个测试类完成的。

我的想法是我不应该丢弃集成测试,所以我正在考虑将测试套件拆分为 UnitTestSuite 和 IntegrationTestSuite。事实上,模拟测试并没有测试所有内容,例如它们不测试查询是否正确。

另外,这两个套件之间的唯一区别是初始数据库重置和 setUpBeforeClass() 代码。仅仅为了改变一个方法而复制和改变所有的测试类是一种浪费。初始 DB 重置很容易跳过,我只是没有在单元测试套件中包含 db reset 测试类。

要拆分单元测试和集成测试,您有什么建议?扩展所有原始类以覆盖静态方法,然后在套件中包含正确的类?

或其他方法?你是怎么做的,或者你会怎么做?

【问题讨论】:

【参考方案1】:

永远记住,单元测试(使用模拟)背后的想法是戳戳和刺激单个类的所有阴暗角落。它们是关于确保类对于您能想到的所有类型的输入都按预期运行。这就是为什么我们使用模拟来执行此操作,因为我们可以对这些模拟进行编程,以执行如果类与应用程序的其余部分挂钩可能难以重现的各种事情。

另一方面,集成有不同的侧重点。这是为了确保您的所有课程一起工作以产生预期的结果。因此,在编码时请记住这一点。这是你所追求的大局。您无需担心单个类的模糊边缘情况,因为这是您的模拟单元测试的工作。但是像数据库状态这样的东西正是集成测试很重要的原因。

我也同意 @stivlo 关于使用 HSQL 等产品进行内存数据库工作的观点。它们既可以加快集成测试的速度,又可以帮助确保一个已知的测试起点。

我建议将您的单元测试和集成测试保存在单独的目录中,或者至少单独命名以匹配的包。很简单,因为它可以帮助您记住您正在处理的内容。

还要注意测试蠕变。 IE。实例化大量应用程序类的单元测试确实应该转移到集成测试中,以及对类进行大量详细变体样式测试的集成测试 - 单元测试的候选者。特别是最后一个,如果存在不应该存在的集成测试,您可以节省大量时间。

最后,像任何代码一样,测试代码时常需要一点爱和关注。因此,请留意重构机会。话虽如此,我也看到了测试代码变得难以使用,因为它过于笼统和过度设计。请记住,测试代码不是生产代码,因此在大多数情况下,清晰度远比工程重要。这是您将通过经验获得的平衡。

【讨论】:

赞成关于“测试蠕变”的段落。集成测试和单元测试的不同之处通常比使用 mock 更多;单元测试涵盖单个类合同,集成测试涵盖(部分)用例针对整个系统。 谢谢你,Derek,所以实际上你说运行与集成和单元测试相同的测试可能不是一个好主意,只是覆盖一个方法,因为我会错过测试的不同重点,我可能会过度设计测试,所以我不妨用一些代码重复来权衡这一点。这是正确的吗? 是的。我相信有些人会不同意代码重复,但如果它有助于保持清晰,我不介意一点重复。这在很大程度上取决于您的测试。如果你不花 30 分钟浏览它们背后的代码库就无法阅读和理解它们,那么它们就太复杂了。另一方面,如果您看到在包含单元测试的类中执行相同的设置 4 或 5 次,那么它可能可以重构为抽象或 setup() 方法。正如我所说,这是基于代码的个人电话。最好的指南是 KISS。【参考方案2】:

我建议使用内存数据库进行集成测试,而不是模拟对象。

对于单元测试,我假设您使用数据库作为存根向测试提供一些数据,并在您的测试调用类似saveMyDomainObject() 时作为模拟。在第一种情况下,您只能模拟特定测试实际需要的内容,而不是整个数据库设置。对于第二种情况,您应该使用 Mockito 的 validate 来检查预期的行为是否正确。

【讨论】:

好吧,实际上我正在使用仅测试数据库模式进行集成测试,我在每次测试运行时重新加载(真正的数据库包含数百万条记录,但我在测试中只有几千条记录db,所以不需要很长时间)。我正在使用,或者更好地开始使用模拟对象进行单元测试。使用内存数据库的优势可能是速度更快,但意味着需要额外的努力来消除 SQL 不兼容性,另外意味着我不再在真实平台上进行测试,所以它也可以在memory db 在真实平台上因 SQL 差异而失败。

以上是关于Junit/Mockito:选择使用模拟或集成测试运行测试的主要内容,如果未能解决你的问题,请参考以下文章

MockEJB - JUnit Mockito - 无法在第二个单元测试中重新绑定模拟 EJB

JUnit + Mockito 单元测试

JUnit、Mockito 和 Spring ApplicationContext:模拟属性时遇到问题

Junit mockito 测试Controller层方法有Pageable异常

Junit mockito

如何使用 Junit + Mockito 实践单元测试