我们应该如何测试代码中的随机性?
假设我们有一个PersonGenerator必须generateRandom生成随机Person实例。
我们首先 卡塔尔电话号码库 编写以下内容:
类 PersonGeneratorShould {
私有 PersonGenerator 生成器 = 新的 PersonGenerator();
@测试
void generateValidPerson() {
人 person = 生成器.generateRandom();
断言(人)。
}
}
然后我们应该问自己:
我在这里想证明什么?这个功能需要做什么?
我是否应该验证生成的人是一个非空实例?
我需要证明它是随机的吗?
生成的实例是否必须遵循一些业务规则?
我们可以使用依赖注入来简化我们的测试。
公共接口 RandomGenerator {
字符串生成随机字符串();
int 生成随机整数();
}
现在PersonGenerator有另一个构造函数,它也接受该接口的实例。默认情况下,它JavaRandomGenerator使用生成随机值的实现java.Random。
然而,在测试中,我们可以编写另一个更可预测的实现。
@测试
void generateValidPerson() {
RandomGenerator randomGenerator = new PredictableGenerator("John Doe", 20);
PersonGenerator 生成器 = 新的 PersonGenerator (randomGenerator);
人 person = 生成器.generateRandom();
断言(person).isEqualTo(新Person("John Doe", 20));
}
此测试证明PersonGenerator生成了 所指定的随机实例,RandomGenerator而无需涉及 的任何细节RandomGenerator。
测试JavaRandomGenerator并没有真正增加任何价值,因为它只是 的简单包装器java.Random。通过测试它,您实际上是java.Random在测试 Java 标准库。编写明显的测试只会导致额外的维护,而几乎没有任何好处。
为了避免为了测试目的而编写实现,例如PredictableGenerator,您应该使用模拟库,例如 Mockito。
当我们编写时PredictableGenerator,我们实际上是RandomGenerator手动对类进行存根。 您也可以使用 Mockito 对它进行存根:
@测试
void generateValidPerson() {
随机生成器 randomGenerator = mock(RandomGenerator.class);
当(randomGenerator.generateRandomString())时,然后返回(“John Doe”);
当(randomGenerator.generateRandomInteger())时,返回(20);
PersonGenerator 生成器 = 新的 PersonGenerator (randomGenerator);
人 person = 生成器.generateRandom();
断言(person).isEqualTo(新Person("John Doe", 20));
}
这种编写测试的方式更具表现力,并且减少了特定测试的实现。
Mockito 是一个用于编写模拟和存根的 Java 库。它在测试依赖于您无法轻松实例化的外部库的代码时非常有用。它允许您为这些类编写行为,而无需直接实现它们。
当我们有多个类似于我们习惯的测试时,Mockito 还允许使用另一种语法来创建和注入模拟以减少样板:
@ExtendWith(MockitoExtension.class)// 1
类 PersonGeneratorShould {
@Mock // 2
随机生成器 随机生成器;
@InjectMocks // 3
私人 PersonGenerator 生成器;
@测试
void generateValidPerson() {
当(randomGenerator.generateRandomString())时,然后返回(“John Doe”);
当(randomGenerator.generateRandomInteger())时,返回(20);
人 person = 生成器.generateRandom();
断言(person).isEqualTo(新Person("John Doe", 20));
}
}
1. JUnit 5 可以使用“扩展”来扩展其功能。此注解允许它通过注解识别模拟并正确注入它们。
2.注释创建该字段的模拟实例。这与在测试方法主体中@Mock编写相同。mock(RandomGenerator.class)
3.@InjectMocks注释将创建一个新的实例PersonGenerator,并在该实例中注入模拟generator。
有关 JUnit 5 扩展的更多详细信息请参见此处。
有关 Mockito 注入的更多详细信息请参见此处。
使用 有一个缺陷@InjectMocks。它可能消除了手动声明对象实例的需要,但我们失去了构造函数的编译时安全性。如果在任何时候有人向构造函数添加另一个依赖项,我们不会在这里得到编译时错误。这可能会导致难以检测的测试失败。我更喜欢使用@BeforeEach来手动设置实例