协作对象对测试带来的问题
在本节中,我们将了解随着我们的软件发展成一个更大的代码库时出现的挑战。我们将回顾协作对象的含义,然后我们将看看两个难以测试的协作示例。
随着我们的软件系统的发展,我们很快就会超出单个类(或函数)所能容纳的范围。我们将把代码分成多个部分。如果我们选择一个对象作为我们的测试对象,那么它依赖的任何其他对象都是协作者。我们的 TDD 测试必须考虑到这些协作者的存在。有时,这很简单,正如我们在前面的章节中看到的那样。
不幸的是,事情并不总是那么简单。有些协作使测试难以——甚至不可能——编写。这些类型的协作者引入了我们必须应对的不可重复行为,或者提出了难以触发的错误。
让我们通过一些简短的示例来回顾这些挑战。我们将从一个常见问题开始:一个表现出不可重复行为的协作者。
测试不可重复行为的挑战
我们已经了解到 TDD 测试的基本步骤是 Arrange
、Act
和 Assert
。我们要求对象行动,然后断言预期的结果会发生。但是,当结果不可预测时会发生什么?
为了说明这一点,让我们回顾一个掷骰子并显示我们掷出的数字的文本字符串的类:
package examples;
import java.util.random.RandomGenerator;
public class DiceRoll {
private final int NUMBER_OF_SIDES = 6;
private final RandomGenerator rnd = RandomGenerator.getDefault();
public String asText() {
int rolled = rnd.nextInt(NUMBER_OF_SIDES) + 1;
return String.format("You rolled a %d", rolled);
}
}
这段代码足够简单,其中只有几行可执行代码。遗憾的是,编写简单并不总是测试简单。我们如何为这个编写测试?具体来说——我们如何编写断言?
在之前的测试中,我们总是确切地知道断言中期望什么。在这里,断言将是一些固定文本加上一个随机数。我们事先不知道那个随机数会是什么。
测试错误处理的挑战
测试处理错误条件的代码是另一个挑战。这里的困难不在于断言错误是否被处理,而在于如何触发协作对象内部发生该错误。
为了说明这一点,让我们想象一个代码,当我们的便携设备电池电量低时警告我们:
public class BatteryMonitor {
public void warnWhenBatteryPowerLow() {
if (DeviceApi.getBatteryPercentage() < 10) {
System.out.println("Warning - Battery low");
}
}
}
前面的 BatteryMonitor
代码中有一个 DeviceApi
类,这是一个库类,让我们可以读取手机上剩余的电池电量。它提供了一个静态方法来实现这一点,称为 getBatteryPercentage()
。这将返回一个 0
到 100%
的整数。我们想要为其编写 TDD 测试的代码调用 getBatteryPercentage()
,如果它小于 10%
,将显示警告消息。但编写这个测试有一个问题:我们如何强制 getBatteryPercentage(
) 方法作为 Arrange
步骤的一部分返回一个小于 10
的数字?我们会以某种方式放电吗?我们该怎么做?
BatteryMonitor
提供了一个与另一个对象协作的代码示例,其中无法强制从该协作者获得已知响应。我们无法更改 getBatteryPercentage()
将返回的值。我们实际上必须等到电池放电后,这个测试才能通过。这不是 TDD 的目的。