集成测试,但是多少?

Integration tests, but how much?

最近我团队内部的一场辩论让我感到疑惑。基本主题是 functional/integration 测试我们应该涵盖多少和什么(当然,它们不一样,但是这个例子是虚拟的,这无关紧要)。

假设您有一个 "controller" class 类似的东西:

public class SomeController {
    @Autowired Validator val;
    @Autowired DataAccess da;
    @Autowired SomeTransformer tr;
    @Autowired Calculator calc;

    public boolean doCheck(Input input) {
        if (val.validate(input)) {
             return false;
        }

        List<Stuff> stuffs = da.loadStuffs(input);
        if (stuffs.isEmpty()) {
             return false;
        }

        BusinessStuff businessStuff = tr.transform(stuffs);
        if (null == businessStuff) {
            return false;
        }

       return calc.check(businessStuff);
    }
}

我们肯定需要大量的单元测试(例如,如果验证失败,或者数据库中没有数据,...),这是不可能的。

我们的主要问题和我们无法达成一致的是,集成测试应涵盖多少:-)

我认为我们的目标是减少集成测试(测试金字塔)。我将从中涵盖的只是一个快乐-不快乐的路径,从最后一行开始执行 returns,只是为了看看我是否将这些东西放在一起它不会爆炸。

问题是要说出为什么测试结果为 false 并不容易,这让一些人对此感到不安(例如,如果我们只检查 return 值, 隐藏测试是绿色的,因为有人更改了验证并且它 returns false)。当然,是的,我们可以涵盖所有情况,但恕我直言,那将是一个严重的矫枉过正。

有没有人对这类问题有很好的经验法则?还是推荐?读?讲话?博客 post?有什么主题吗?

提前致谢!

PS:抱歉这个丑陋的例子,但是很难将特定的代码部分转换成一个例子。是的,关于抛出 exceptions/using 不同的 return type/etc 是有争议的。但由于外部依赖,我们的手或多或少受到了束缚。

如果您遵循以下规则,很容易找出测试应该驻留的位置:

  • 我们在单元测试级别检查逻辑,并在组件或系统级别检查逻辑是否被调用
  • 我们不使用模拟框架(mockito、jmock 等)。

让我们深入探讨,但首先让我们就术语达成一致:

  • 单元测试 - 检查一个方法,一个 class 或孤立的几个方法
  • 组件测试 - 初始化应用程序的一部分,但不将其部署到应用程序服务器。示例可能是 - 在测试中初始化 Spring 上下文。
  • 系统测试 - 需要在 App Server 上进行完整部署。示例可能是:将 HTTP REST 请求发送到远程服务器。

如果我们构建一个平衡的金字塔,我们将在单元和组件级别进行大部分测试,而其中很少一部分将留给系统测试。这很好,因为较低级别的测试更快更容易。为此:

  • 我们应该将业务逻辑放在尽可能低的位置(最好在域模型中),因为这将使我们能够轻松地对其进行隔离测试。每次你浏览一组对象并在那里放置条件时 - 理想情况下应该转到域模型。
  • 但逻辑有效并不意味着它被正确调用。这就是您需要组件测试的地方。初始化你的Controllers以及services和DAO,然后调用一次或两次,看看是否调用了逻辑。

例如:用户名不能超过50个符号,只能有拉丁文和一些特殊符号。

  • 单元测试 - 使用正确和错误的用户名创建用户,检查是否抛出异常,反之亦然 - 有效名称通过
  • 组件测试 - 检查当您将无效用户传递给控制器​​时(如果您使用 Spring MVC - 您可以使用 MockMVC 做到这一点)它会抛出错误。在这里您只需要传递一个用户 - 现在已经检查了所有规则,在这里您只想知道是否调用了这些规则。
  • 系统测试 - 实际上在这种情况下您可能不需要它们..

Here is a more elaborate example 如何实现平衡金字塔。

一般来说,我们会在应用程序的每个起点(假设每个控制器)编写一个集成测试。我们验证了一些满意的流程和一些错误的流程,并通过一些断言让我们高枕无忧,因为我们没有破坏任何东西。

但是,我们也会在较低级别编写测试以响应回归或多个 classes 涉及一个复杂的行为。

我们主要使用集成测试来捕获以下类型的回归:

  1. 重构错误(未被单元测试发现)。

对于重构问题,对应用程序的很大一部分进行几次 IT 测试就绰绰有余了。重构通常会触及很大一部分 classes,因此这些测试会暴露诸如在某处使用错误的 class 或参数之类的问题。

  1. 注入问题的早期检测(上下文未加载,Spring)

注入问题经常发生是因为 XML 配置中缺少注释或错误。运行并设置整个上下文(除了模拟后端)的第一个集成测试每次都会捕获这些。

  1. 超级复杂的逻辑中的错误,在不控制所有输入的情况下几乎无法测试

有时您的代码分布在多个 class 中,需要过滤、转换等,有时没有人真正理解发生了什么。更糟糕的是,几乎不可能在实时系统上进行测试,因为底层数据源无法轻易提供将触发错误的确切场景。

对于这些情况(一旦发现),我们添加了一个新的集成测试,我们向系统提供导致错误的输入,然后验证它是否按预期执行。在大量代码更改后,这让您高枕无忧。