每层集成测试是一个好习惯?

Integration test per layer is a good practice?

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

根据 class/component 进行单元测试,基本上我们试图很好地覆盖这些组件中的传入输入和可能的流程,并且此操作运行良好,这里没有疑问,因为单元测试是关于确保设备按预期工作。

集成测试是另一回事,而且很有争议,因为现在我们有时使用与设计单元测试相同的场景,但整个系统都可以使用真实平台等等,但我对此表示怀疑最佳实践在这里。

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

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

  3. 如果我们有一个帮手 class 我认为拥有单元和 IT 没有意义,因为大多数方法只有一个目的,我认为单元测试将是到这里就够了,你觉得一样吗?。

  4. 数据层中的一些classes可以使用Criteria API,对于那些我去使用IT的人来说QueryDSL使得单元测试在某些情况下非常困难,这是正当理由吗?

我正在努力寻找最好的方法、技巧和实践,使确保系统完整性的任务成为一个真实而有价值的过程,同时牢记这些东西的可维护性。

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

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

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

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

单元测试适用于 classes 和组件。其目的是:

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

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

仅当需要测试 class 或组件独立行为并且模拟功能来自应用程序外部(例如电子邮件服务器)的生产时,才应使用模拟。当代码已经被覆盖并且模拟与其他类型的测试(例如集成测试)的职责重叠时,它是矫枉过正且无用的。

既然您知道每段代码都可以工作,那么这些代码是如何协同工作的? 这就是集成测试的来源,它是关于组件如何在各种条件和环境中交互的。有时 UT 和 IT 之间几乎没有区别:例如数据访问层的测试。 集成测试用于与单元测试相同的目的,但在更高级别,原子性较低,以说明 API、服务的用例...

你怎么称呼 "integration layer"?

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

你也没说性能测试。

最后,目标是将所有代码与测试一起编写,在使用新测试重现后修复错误,并最大程度地覆盖所有可能条件下的各种测试(OS、数据库、浏览器) ...)。 因此,您通过以下方式验证您的整体测试质量:

  • 一个计算覆盖率的工具。您可能必须检测代码以评估功能测试的覆盖率或使用高级 JDK 工具。
  • 由于某些组件、服务缺乏测试而导致的错误数量...

我通常认为一堆测试很好,当我阅读它们时,我立即对如何使用它们涵盖的代码毫无疑问,并对它的合同、功能、输入和输出、历史和用例的穷尽性充满信心,错误管理和报告方面的强度和稳定性。

覆盖率也不是一件重要的事情,但如果您关注它们的质量,最好少做一些测试:线程安全,由无序方法和 classes 组成,测试真实条件(没有 "if test condition" 黑客)。

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

您有点涉及您的应用程序所需的整个测试策略。测试不仅仅是关于覆盖率和层数。例如:

we want to ensure using testing that future addition to the code won't break nothing that was previously working, to do this we are using unit testing(mockito) and integration testing (spring-test,spring-test-mvc).

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

很容易看出集成测试占了很大一部分(建议5-15%)。集成跨层,也跨组件APIs。您的业​​务组件自然会位于同一层中,但您仍然需要确保它们也按预期相互工作。拥有 mSOA 将推动您支持如此广泛的接口集成测试。

我同意你的观点

Integration test is different story and very debatable

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

I feel sometimes we repeat the same test scenario but now using the real system. Does this practice really make sense or in which scenarios this makes sense ? If we already have 100% test coverage in the class with unit testing why we need IT for this one, seems that we have this only to ensure real component is going to start-up ? Make sense to have all the same scenarios or which is a good criteria to decide?

看来你需要在那些场景中划清界限。长话短说 - 单元测试和 Mock objects 自然而然地结合在一起。组件测试将需要一些真实的系统行为,它可用于检查在各个单元或子系统组件之间传递的数据的处理 - 例如您的 component/service 数据库或不是单元级任务的消息传递。

from the controller layer, which it means invoke the REST services and verify the JSON output. This is enough ?, We don't need to verify more things in the others layers ?. I know calling the real REST api will use underneath all the layers (controller, service, dao) but this is enough ?

不完全正确 - 测试表示层也会对底层进行测试...那么为什么要费心进行所有其他测试呢?如果您接受这种方法 - Selenium 团队建议使用这种 DB validation 方法。

如果你在这里谈论 Beans and ViewHelpers

we have a helper class I don't think make sense to have unit and IT, as most of the method as there for only one purpose I think unit testing will be enough here, does you think the same?.

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

make the unit testing in some cases is extremely difficult, this is a valid justification?

同样适用于所有封装的私有(和静态)类、方法、属性等。但是也有一种测试它们的方法——比如 reflection。这当然是针对单元测试遗留代码或无法更改的 API 的特殊情况。如果您自己的代码需要它,那么这种可测试性的缺乏可能表明存在设计异味。