当前位置:Java -> 提高单元测试的可维护性

提高单元测试的可维护性

在进行单元测试时,你可能会发现自己经常不得不反复创建对象的情况。为了做到这一点,你必须使用相应的参数调用类构造函数。到目前为止,还没有什么异常,但很可能有时候这些字段的值对于测试是无关紧要的,或者因为构造函数要求必须创建嵌套的“虚拟”对象。

所有这些可能在某个时候引起了一些挫折,并让你质疑你是否做得对;如果这才是进行单元测试的正确方式,那么可能得不偿失。

也就是说,通常一个测试必须有一个明确的目标。因此,可以预期在SUT(正在进行测试的系统)中,有一些字段是测试对象,另一些则是无关紧要的。

让我们举个例子。假设我们有一个名为"Person"的类,其中有NameEmailAge三个字段。另外,我们想对一个服务进行单元测试,该服务接收一个Person对象,并告诉我们这个人是否可以免费乘坐公交车。我们知道这个计算只取决于年龄。14岁以下的孩子可以免费乘坐。因此,在这种情况下,NameEmail字段是无关紧要的。

在这个例子中,创建Person对象可能并不需要太大的努力,但假设Person类的字段不断增加或者开始出现嵌套对象:AddressRelativesPeople列表)、Phone List等。现在,有几个问题需要考虑:

  • 创建这些对象更加费力。
  • 当构造函数或类的字段发生变化时会发生什么?
  • 当存在对象列表时,我应该创建多少个对象?
  • 应该给那些不影响测试的字段赋予什么值?
  • 如果这些值总是相同的,没有任何变化,这样做好吗?

通常,通常会使用两种广为人知的设计模式来解决这种情况:对象生成器和生成器。在这两种方法中,主要的思路是使用“助手”来简化我们所需对象的创建过程。

这两种方法都很普遍,是适当的,并且有利于测试的可维护性。然而,它们仍然没有解决一些问题:

  • 当更改构造函数时,即使影响到测试的字段,代码也将停止编译。
  • 当出现新字段时,我们必须更新生成测试对象的代码。
  • 生成嵌套对象仍然费力。
  • 强制性和未使用的字段是硬编码的,并且默认情况下分配,所以测试没有变化性。

其中一个可以解决这些问题的Java库是EasyRandom。接下来,我们将看到它的工作细节。

什么是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库具有以下几个优势:

  1. 简化随机数据生成:它自动为你的对象生成随机数据,避免了为每个测试编写重复代码。
  2. 简化单元测试和集成测试:通过自动生成测试对象,你可以专注于测试代码的行为,而不必担心手动创建测试数据。
  3. 数据定制:尽管它默认生成随机数据,但EasyRandom也允许你根据需要为某些字段或属性定制数据,允许你根据需要调整生成。
  4. 减少人为错误:手动生成测试数据可能会导致错误,特别是当涉及到许多字段和组合时。EasyRandom通过生成一致的随机数据来帮助最小化人为错误。
  5. 简化维护:如果你的类需求发生变化(新的字段、类型等),你不需要手动更新测试数据,因为EasyRandom会自动生成它们。
  6. 提高可读性:使用EasyRandom使你的测试更清晰、更可读,因为你不需要在每种情况下明确定义测试值。
  7. 更快的测试开发:通过减少创建测试对象的时间,你可以更快、更有效地开发测试。
  8. 易于使用:向我们的Java项目添加这个库几乎是立即的,并且非常易于使用。

你可以在哪里应用它?

这个库将允许我们简化单元测试中对象的创建,但当我们需要生成一组测试数据时,它也会提供很大的帮助。这可以通过使用我们应用程序的DTOs,生成随机对象然后将它们转储到数据库或文件中来实现。不建议在这种情况下使用:在对象生成不复杂或者我们需要精确控制测试中涉及对象的所有字段的项目中,这个库可能并不值得。

如何使用EasyRandom

让我们通过一个实例、使用的环境和先决条件来看EasyRandom的工作情况。

先决条件

  • Java 8+
  • Maven或Gradle

初始设置

在我们的项目中,我们必须添加一个新的依赖项。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找它们的中位数

本文链接: 提高单元测试的可维护性