每层集成测试是一种很好的做法吗?

时间:2015-10-06 16:20:00

标签: java spring unit-testing spring-mvc integration-testing

我有一个使用spring-mvc的应用程序,基本上我们有一个表示层(控制器),服务层(业务单元,帮助器),集成层和数据访问层(jdbc / jpa存储库),我们希望确保使用测试代码的未来添加不会破坏以前工作的任何东西,为此我们使用单元测试(mockito)和集成测试(spring-test,spring-test-mvc)。

对每个类/组件进行单元测试,基本上我们试图对输入输入和这些组件中的可能流进行良好的覆盖,这个动作工作正常,这里没有疑问,因为单元测试是关于确保单元工作如预期的那样。

集成测试是一个不同的故事和非常有争议的一个,因为现在我们有时使用相同的场景来设计我们的单元测试,但是使用真实平台等可以获得整个系统,但我对最好的情况有疑问在这里练习。

  1. 由于我们有一个控制器,服务,数据层一种方法是每层IT,例如我们有UserService类,我们将有UserServiceTest,它将是Unit测试和UserServiceIT,但可维护性不理想,我觉得有时我们重复相同的测试场景,但现在使用真实的系统。这种做法真的有意义吗或在哪种情况下这是有意义的?如果我们已经在课堂上有100%的测试覆盖率进行单元测试,为什么我们需要IT来实现这一目标,那么我们认为这只是为了确保真正的组件能够启动?有意义的是拥有所有相同的场景或哪些是一个很好的判断标准吗?

  2. 其他方法只是通过集成测试来处理最重要的测试用例,而只是来自控制器层,这意味着调用REST服务并验证JSON输出。这够了吗?我们不需要在其他层中验证更多东西吗?我知道调用真正的REST api将在所有层(控制器,服务,dao)下使用,但这已经足够了吗?你会在这里说些什么?

  3. 如果我们有一个帮助班,我不认为有单位和IT是有意义的,因为大多数方法只有一个目的我觉得单元测试就够了,你认为吗?相同?。

  4. 数据层中的某些类可以使用Criteria API,QueryDSL用于那些我使用IT的人,因为在某些情况下进行单元测试非常困难,这是一个有效的理由吗?

  5. 我正在努力获得最佳方法,提示和实践,使确保系统完整性的任务成为真正有价值的流程,同时牢记这些内容的可维护性。

3 个答案:

答案 0 :(得分:7)

您需要触摸应用程序所需的整个测试策略。测试不仅涉及覆盖范围和层次。例如:

  

我们希望确保使用测试,未来的代码添加不会破坏以前的工作,为此,我们使用单元测试(mockito)和集成测试(spring-test,spring-test- MVC)。

这就是你实际支持Regression testing的方式,这是一种类型。如果我们看一下(详细的)测试金字塔

Test pyramid

很容易看出集成测试占了很大比例(建议5-15%)。集成是跨层的,但也是跨组件的API。您的业​​务组件将位于同一层,这很自然,但您仍需要确保它们彼此之间的工作正常。 mSOA将促使您支持这种广泛的接口集成测试。

我同意你的意见

  

集成测试是不同的故事和非常有争议的

有些专家甚至建议您只保留单元测试和GUI E2E。恕我直言,没有严格的最佳做法 - 只有好的做法。如果您对权衡取舍感到满意,请使用适合您情况的产品。

  

我觉得有时我们会重复相同的测试场景,但现在使用真实系统。这种做法真的有意义吗或在哪种情况下这是有道理的?如果我们已经在课堂上进行了100%的测试覆盖,并且单元测试为什么我们需要IT来实现这一目标,那么我们认为这只是为了确保真正的组件能够启动吗?有没有理由拥有所有相同的场景或哪个是一个好的标准来决定?

看起来你需要在这些场景中划一条线。长话短说 - 单元测试和Mock objects自然地结合在一起。组件测试需要一些真实的系统行为,它可用于检查各个单元或子系统组件之间传递的数据的处理 - 如组件/服务DB或非单元级任务的消息传递。

  来自控制器层的

,这意味着调用REST服务并验证JSON输出。这够了吗?我们不需要在其他层中验证更多东西吗?我知道调用真正的REST api将在所有层(控制器,服务,dao)下使用,但这已经足够了吗?

不完全正确 - 测试表示层也会运行底层...所以为什么还要考虑所有其他测试?如果你对这种方法没问题--Selenium团队提出了这样的DB validation方法。

如果你在这里谈论BeansViewHelpers

  

我们有一个帮助班我不认为有单位和IT是有意义的,因为大多数方法只有一个目的我觉得单元测试就够了,你觉得一样吗?

您需要单位和IT,因为所有原因都适用于其他组件。 Single responsibility并不否认需要进行IT测试。

  

在某些情况下进行单元测试是非常困难的,这是一个有效的理由吗?

同样适用于所有封装的私有(和静态)类,方法,属性等。但是也有一种方法可以测试它们 - 比如reflection。这当然是针对特殊情况的单元测试遗留代码或您无法改变的API。如果你需要它来代替自己的代码,那么缺乏可测试性可能会导致设计气味。

答案 1 :(得分:2)

根据最近测试Java EE 7和基于Spring的代码库的经验,我建议的方法是:

使用按功能集成测试,并避免单元测试和模拟。每个集成测试应涵盖所有层的代码,从表示层到基础结构层(这个包含可重用的组件,这些组件不是应用程序或域特定的,但适用于所选的体系结构)。

大多数集成测试应基于实际业务需求和输入数据。根据每次执行集成测试套件生成的代码覆盖率报告,可以创建其他部分来执行代码库的剩余部分。

所以,假设"完整"代码覆盖是通过集成测试实现的,它们运行得足够快,根本没有理由进行单元测试。我的经验是,在编写单元测试时,开发人员倾向于使用过多的模拟,经常创建脆弱的测试来验证不必要的实现细节。此外,单元测试永远不会像集成测试那样提供相同的置信度,因为它们通常不会涵盖数据库查询,ORM映射等内容。

答案 2 :(得分:2)

单元测试适用于您对类和组件所做的操作。其目的是:

  • 编写代码(TDD)。
  • 说明代码使用情况,并使其随着时间的推移和变化而持续发展。
  • 尽可能多地覆盖边境情况。

当您遇到某些特定用法或参数的问题时,请先使用新测试重现该问题,然后进行修复。

只有在需要测试类或组件独立行为并且从应用程序外部(例如电子邮件服务器)生成模拟功能时,才应使用模拟。当代码已经被覆盖并且模拟与其他类型的测试(例如集成测试)的责任重叠时,它是过度和无用的。

既然您知道每一段代码都有效,那么这些代码如何协同工作? 这是集成测试的地方,该测试涉及组件在各种条件和环境中如何相互作用。 UT和IT之间的差异有时很小:例如数据访问层的测试。 集成测试用于与单元测试相同的目的,但在更高级别,更少原子,用于说明API,服务的用例......

你称之为"整合层"?

表示层测试是功能测试的责任,而不是单元或集成。

您也没有谈论性能测试。

最后,目标是将所有代码与测试一起编写,在使用新测试进行复制后修复错误,并在所有可能条件(OS,数据库,浏览器......)中累积所有类型的测试。 因此,您可以通过以下方式验证整体测试质量:

  • 计算覆盖范围的工具。您可能需要检测代码以评估功能测试的覆盖范围或使用高级JDK工具。
  • 因某些组件,服务缺乏测试而导致的错误数量......

我通常认为在阅读它们时,一堆测试是好的,这让我立即毫不怀疑如何使用它们所涵盖的代码,并对其合同,功能,输入和输出,历史和用例的可耗性,强度和关于错误管理和报告的稳定性。

覆盖范围也不是一件很重要的事情,但如果你专注于它们的质量,那么减少一些测试可能会更好:线程安全,由无序方法和类组成,测试真实条件(没有"如果测试条件"黑客)。

回答你的问题:我想说,鉴于上述考虑因素,你不必为每一层编写集成测试,因为你宁愿选择不同的测试策略(单位,集成,功能,性能,烟雾,嘲笑...)每层。