集成测试,但是多少?
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 涉及一个复杂的行为。
我们主要使用集成测试来捕获以下类型的回归:
- 重构错误(未被单元测试发现)。
对于重构问题,对应用程序的很大一部分进行几次 IT 测试就绰绰有余了。重构通常会触及很大一部分 classes,因此这些测试会暴露诸如在某处使用错误的 class 或参数之类的问题。
- 注入问题的早期检测(上下文未加载,Spring)
注入问题经常发生是因为 XML 配置中缺少注释或错误。运行并设置整个上下文(除了模拟后端)的第一个集成测试每次都会捕获这些。
- 超级复杂的逻辑中的错误,在不控制所有输入的情况下几乎无法测试
有时您的代码分布在多个 class 中,需要过滤、转换等,有时没有人真正理解发生了什么。更糟糕的是,几乎不可能在实时系统上进行测试,因为底层数据源无法轻易提供将触发错误的确切场景。
对于这些情况(一旦发现),我们添加了一个新的集成测试,我们向系统提供导致错误的输入,然后验证它是否按预期执行。在大量代码更改后,这让您高枕无忧。
最近我团队内部的一场辩论让我感到疑惑。基本主题是 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 涉及一个复杂的行为。
我们主要使用集成测试来捕获以下类型的回归:
- 重构错误(未被单元测试发现)。
对于重构问题,对应用程序的很大一部分进行几次 IT 测试就绰绰有余了。重构通常会触及很大一部分 classes,因此这些测试会暴露诸如在某处使用错误的 class 或参数之类的问题。
- 注入问题的早期检测(上下文未加载,Spring)
注入问题经常发生是因为 XML 配置中缺少注释或错误。运行并设置整个上下文(除了模拟后端)的第一个集成测试每次都会捕获这些。
- 超级复杂的逻辑中的错误,在不控制所有输入的情况下几乎无法测试
有时您的代码分布在多个 class 中,需要过滤、转换等,有时没有人真正理解发生了什么。更糟糕的是,几乎不可能在实时系统上进行测试,因为底层数据源无法轻易提供将触发错误的确切场景。
对于这些情况(一旦发现),我们添加了一个新的集成测试,我们向系统提供导致错误的输入,然后验证它是否按预期执行。在大量代码更改后,这让您高枕无忧。