当前位置:Java -> 提高单元测试的可维护性
在进行单元测试时,你可能会发现自己经常不得不反复创建对象的情况。为了做到这一点,你必须使用相应的参数调用类构造函数。到目前为止,还没有什么异常,但很可能有时候这些字段的值对于测试是无关紧要的,或者因为构造函数要求必须创建嵌套的“虚拟”对象。
所有这些可能在某个时候引起了一些挫折,并让你质疑你是否做得对;如果这才是进行单元测试的正确方式,那么可能得不偿失。
也就是说,通常一个测试必须有一个明确的目标。因此,可以预期在SUT(正在进行测试的系统)中,有一些字段是测试对象,另一些则是无关紧要的。
让我们举个例子。假设我们有一个名为"Person"
的类,其中有Name
、Email
和Age
三个字段。另外,我们想对一个服务进行单元测试
,该服务接收一个Person
对象,并告诉我们这个人是否可以免费乘坐公交车。我们知道这个计算只取决于年龄。14岁以下的孩子可以免费乘坐。因此,在这种情况下,Name
和Email
字段是无关紧要的。
在这个例子中,创建Person
对象可能并不需要太大的努力,但假设Person
类的字段不断增加或者开始出现嵌套对象:Address
、Relatives
(People列表
)、Phone List
等。现在,有几个问题需要考虑:
通常,通常会使用两种广为人知的设计模式来解决这种情况:对象生成器和生成器。在这两种方法中,主要的思路是使用“助手”来简化我们所需对象的创建过程。
这两种方法都很普遍,是适当的,并且有利于测试的可维护性。然而,它们仍然没有解决一些问题:
其中一个可以解决这些问题的Java库是EasyRandom。接下来,我们将看到它的工作细节。
EasyRandom是一个能够简化单元测试和集成测试中的随机数据生成的Java库。EasyRandom的理念是提供一种简单的方式来创建带有随机值的对象,并将其用于测试。它自动为每个属性生成随机数据,而不需要在每个测试中手动定义这些值。该库处理原始数据类型、自定义类、集合和其他类型的对象。它还可以配置为遵守具体规则和数据生成限制,因此非常灵活。
以下是EasyRandom如何生成随机对象的基本示例:
public class EasyRandomExample {
public static void main(String[] args) {
EasyRandom easyRandom = new EasyRandom();
Person randomPerson = easyRandom.nextObject(Person.class);
System.out.println(randomPerson);
}
}
在这个例子中,Person
是一个虚拟类,easyRandom.nextObject(Person.class)
会生成一个具有随机属性值的Person
实例。
可以看到,这些对象的生成并不依赖于类构造函数,因此测试代码会继续编译,即使SUT发生变化。这解决了测试套件维护中的一个最大问题。
在测试应用程序时使用EasyRandom库具有以下几个优势:
这个库将允许我们简化单元测试中对象的创建,但当我们需要生成一组测试数据时,它也会提供很大的帮助。这可以通过使用我们应用程序的DTOs,生成随机对象然后将它们转储到数据库或文件中来实现。不建议在这种情况下使用:在对象生成不复杂或者我们需要精确控制测试中涉及对象的所有字段的项目中,这个库可能并不值得。
让我们通过一个实例、使用的环境和先决条件来看EasyRandom的工作情况。
在我们的项目中,我们必须添加一个新的依赖项。pom.xml文件看起来会是这样:
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-random-core</artifactId>
<version>5.0.0</version>
</dependency>
最基本的用例之前已经见过了。在这个示例中,将值以完全随机的方式分配给了person类的字段。显然,当进行测试时,我们需要控制一些特定的字段。让我们以一个例子来看看。回想一下,EasyRandom也可以与原始类型一起使用。因此,我们的示例可能如下所示。
public class PersonServiceTest {
private final EasyRandom easyRandom = new EasyRandom();
private final PersonService personService = new PersonService();
@Test
public void testIsAdult() {
Person adultPerson = easyRandom.nextObject(Person.class);
adultPerson.setAge(18 + easyRandom.nextInt(80));
assertTrue(personService.isAdult(adultPerson));
}
@Test
public void testIsNotAdult() {
Person minorPerson = easyRandom.nextObject(Person.class);
minorPerson.setAge(easyRandom.nextInt(17));
assertFalse(personService.isAdult(minorPerson));
}
}
如我们所见,生成测试对象的这种方式可以保护我们免受"Person
"类的变化影响,并且允许我们只关注我们感兴趣的字段。
我们还可以使用此库来生成随机对象的列表。
@Test
void generateObjectsList() {
EasyRandom generator = new EasyRandom();
//Generamos una lista de 5 Personas
List<Person> persons = generator.objects(Person.class, 5)
.collect(Collectors.toList());
assertEquals(5, persons.size());
}
从根本上说,这个测试本身并不是非常有用。这只是为了展示生成列表的能力,这些列表可以用来将数据转储到数据库中。
现在让我们看看如何使用这个库来更精确地控制生成对象本身。这可以通过参数化来实现。
设置字段的值。想象一下,对于我们的测试,我们想要保持某些值不变(如ID、姓名、地址等)。为了实现这一点,我们可以使用"EasyRandomParameters
"配置对象的初始化,并根据它们的名称定位参数。
让我们来看看:
EasyRandomParameters params = new EasyRandomParameters();
// Asignar un valor al campo por medio de una función lamba
params.randomize(named("age"),()-> 5);
EasyRandom easyRandom = new EasyRandom(params);
// El objeto tendrá siempre una edad de 5
Person person = easyRandom.nextObject(Person.class);
当然,同样的方法也可以用于集合或复杂对象。
假设我们的Person
类里包含一个Address
类,并且另外,我们想生成两个人的列表。
让我们看一个更完整的例子:
EasyRandomParameters parameters = new EasyRandomParameters()
.randomize(Address.class, () -> new Address("Random St.", "Random City"))
EasyRandom easyRandom = new EasyRandom(parameters);
return Arrays.asList(
easyRandom.nextObject(Person.class),
easyRandom.nextObject(Person.class)
);
现在假设一个人可以有多个地址。这意味着"Address
"字段将是"Person
"类内的列表。
使用这个库,我们还可以使我们的集合具有可变大小。这也是我们可以使用参数来做的事情。
EasyRandomParameters parameters = new EasyRandomParameters()
.randomize(Address.class, () -> new Address("Random St.", "Random City"))
.collectionSizeRange(2, 10);
EasyRandom easyRandom = new EasyRandom(parameters);
// El objeto tendrá una lista de entre 2 y 10 direcciones
Person person = easyRandom.nextObject(Person.class);
正如我们所见,设置值非常简单明了。但是如果我们想控制数据的随机性呢?我们想生成人的随机名字,但仍然是姓名,而不只是无关字符的字符串。当我们希望在字段中具有随机性时,这种需求可能更清晰,比如电子邮件、电话号码、身份证号、卡号、城市名等。
为此,使用其他数据生成库是非常有用的。其中最著名的之一是Faker
。
结合这两个库,我们可以获得如下代码:
EasyRandomParameters params = new EasyRandomParameters();
//Generar número entre 0 y 17
params.randomize(named("age"), () -> Faker.instance().number().numberBetween(0, 17));
// Generar nombre "reales" aleatorios
params.randomize(named("name"), () -> Faker.instance().name().fullName());
EasyRandom easyRandom = new EasyRandom(params);
Person person = easyRandom.nextObject(Person.class);
有许多参数可以控制对象的生成。
EasyRandom是一个应该成为你开发单元测试背包一部分的库,因为它有助于保持单元测试。此外,尽管可能会觉得奇怪,但在测试中建立一些受控制的随机性也未必是坏事。在某种程度上,这是一种自动生成新的测试案例的方式,并且会增加发现代码错误的概率。
推荐阅读: 9. 5亿个int找它们的中位数
本文链接: 提高单元测试的可维护性