11

您如何管理用于测试的虚拟数据?将它们与各自的实体一起保存?在单独的测试项目中?使用来自外部资源的序列化程序加载它们?或者只是在需要的地方重新创建它们?

我们有一个应用程序堆栈,其中有几个模块依赖于另一个模块,每个模块都包含实体。每个模块都有自己的测试,并且需要虚拟数据才能运行。

现在,一个具有很多依赖关系的模块将需要来自其他模块的大量虚拟数据。然而,那些不发布他们的虚拟对象,因为它们是测试资源的一部分,所以所有模块都必须一次又一次地设置他们需要的所有虚拟对象。

另外:我们实体中的大多数字段都不能为空,因此即使针对对象层运行事务也需要它们包含一些值,大多数情况下还有进一步的限制,例如唯一性、长度等。

是否有解决此问题的最佳实践方法,或者所有解决方案都会妥协?


更多详情

我们的堆栈看起来像这样:

一个模块:

src/main/java --> gets jared (.../entities/*.java contains the entities)
src/main/resources --> gets jared
src/test/java --> contains dummy object setup, will NOT get jared
src/test/resources --> not jared

我们使用 Maven 来处理依赖关系。

模块示例:

  • 模块 A有一些虚拟对象
  • 模块 B需要自己的对象并且与模块 A 相同

选项 a)

测试模块T可以保存所有虚拟对象并在测试范围内将它们提供给所有模块中的所有测试(因此加载的依赖项不会受到影响)。那会奏效吗?含义:如果我在A中加载T并在A上运行 install ,它将不包含T引入的引用,尤其是B?然后A会知道B的数据模型。

选项 b)

模块 A 在某处提供虚拟对象以src/main/java../entities/dummy允许B获取它们,而A不知道B的虚拟数据

选项 c)

每个模块都包含外部资源,它们是序列化的虚拟对象。它们可以被需要它们的测试环境反序列化,因为它依赖于它们所属的模块。这将要求每个模块创建和序列化其虚拟对象,但如何做到这一点?如果使用另一个单元测试,它会在单元测试之间引入不应该发生的依赖关系,或者使用脚本将难以调试且不灵活。

选项 d)

使用模拟框架并根据需要为每个测试手动分配必填字段。这里的问题是我们实体中的大多数字段都不能为空,因此需要调用 setter 或构造函数,这将使我们再次开始。

我们不想要的

我们不想用静态数据建立一个静态数据库,因为所需对象的结构会不断变化。现在很多,稍后。因此,我们希望 hibernate 设置所有表和列,并在单元测试时用数据填充它们。此外,静态数据库会引入许多潜在的错误和测试相互依赖性。


我的想法是否朝着正确的方向发展?处理需要大量数据的测试的最佳实践是什么?我们将有几个相互依赖的模块,这些模块需要填充来自其他几个模块的某种数据的对象。


编辑

关于我们现在如何响应第二个答案的更多信息:

因此,为简单起见,我们有三个模块:PersonProductOrderPerson将使用MockPerson对象测试一些管理器方法:

(在人/src/test/java :)

public class MockPerson {

    public Person mockPerson(parameters...) {
        return mockedPerson;
    }
}

public class TestPerson() {
    @Inject
    private MockPerson mockPerson;
    public testCreate() {
        Person person = mockPerson.mockPerson(...);
        // Asserts...
    }
}

该类MockPerson将不会被打包。

这同样适用于产品测试:

(在产品/src/test/java :)

public class MockProduct() { ... }
public class TestProduct {
    @Inject
    private MockProduct mockProduct;
    // ...
}

MockProduct需要,但不会打包。

现在 Order Tests 将需要MockPersonand MockProduct,所以现在我们现在需要同时创建和MockOrderto test Order

(按顺序/src/test/java :)

这些是重复的,每次都需要更改PersonProduct更改

public class MockProduct() { ... }
public class MockPerson() { ... }

这是唯一应该在这里的课程:

public class MockOrder() { ... }

public class TestOrder() {
    @Inject
    private order.MockPerson mockPerson;
    @Inject
    private order.MockProduct mockProduct;
    @Inject
    private order.MockOrder mockOrder;
    public testCreate() {

        Order order = mockOrder.mockOrder(mockPerson.mockPerson(), mockProduct.mockProduct());
        // Asserts...
    }
}

问题是,现在我们必须更新person.MockPerson并且order.MockPerson无论何时Person更改。

使用 jar 发布 Mocks 以便任何其他具有依赖关系的测试都可以调用 Mock.mock 并获得一个很好的设置对象不是更好吗?或者这是黑暗的一面——简单的方法?

4

3 回答 3

3

这可能适用也可能不适用——我很想看看你的虚拟对象和相关设置代码的例子。(为了更好地了解它是否适​​用于您的情况。)但是我过去所做的甚至根本没有将这种代码引入测试。正如您所描述的,很难生产、调试,尤其是打包和维护。

正如Nat Pryce在他的Test Data Builders帖子中所描述的,我通常所做的(并且在 Java 中使用 AFAIKT 这是最佳实践)是尝试使用测试数据生成器模式。

如果您认为这有点相关,请查看以下内容:

于 2012-01-14T01:54:52.647 回答
3

好吧,我仔细阅读了到目前为止的所有评价,这是一个非常好的问题。我看到以下解决问题的方法:

  1. 建立(静态)测试数据库;
  2. 每个测试都有自己的设置数据,在运行单元测试之前创建(动态)测试数据;
  3. 使用虚拟或模拟对象。所有模块都知道所有虚拟对象,这样就没有重复;
  4. 减少单元测试的范围;

第一个选项非常简单并且有很多缺点,有人必须偶尔重现它,当单元测试“搞砸”时,如果数据模块发生变化,有人必须对测试数据进行相应的更改,大量的维护开销。并不是说直接生成这些数据可能很棘手。另见第二个选项。

第二种选择,您编写测试代码,在测试之前调用一些创建实体的“核心”业务方法。理想情况下,您的测试代码应该独立于生产代码,但在这种情况下,您将得到重复的代码,您应该支持两次。有时,最好拆分您的生产业务方法以便为您的单元测试提供入口点(我将这些方法设为私有并使用反射来调用它们,还需要对方法进行一些说明,重构现在有点棘手) . 主要缺点是如果您必须更改“核心”业务方法,它会突然影响您的所有单元测试并且您无法测试。因此,开发人员应该意识到这一点,并且不要对“核心”业务方法进行部分提交,除非它们有效。还,随着这方面的任何变化,您应该牢记“它将如何影响我的单元测试”。有时,也无法动态复制所有需要的数据(通常是因为第三方 API,例如,您使用自己的数据库调用另一个应用程序,您需要从中使用一些密钥。这些密钥(与关联数据)是通过第三方应用手动创建的,这种情况下,只有这个数据,应该是静态创建的,比如你创建了10000个key,从300000开始。s 自己的数据库,您需要从中使用一些密钥。此密钥(带有相关数据)是通过第三方应用程序手动创建的。在这种情况下,应该静态创建该数据并且仅创建该数据。例如,您从 300000 开始创建了 10000 个密钥。s 自己的数据库,您需要从中使用一些密钥。此密钥(带有相关数据)是通过第三方应用程序手动创建的。在这种情况下,应该静态创建该数据并且仅创建该数据。例如,您从 300000 开始创建了 10000 个密钥。

第三个选项应该不错。选项 a) 和 d) 对我来说听起来不错。对于您的虚拟对象,您可以使用模拟框架,也可以不使用它。模拟框架在这里只是为了帮助你。我不认为您的所有单位都知道您的所有实体的问题。

第四个选项意味着您在单元测试中重新定义什么是“单元”。当您有几个相互依赖的模块时,很难单独测试每个模块。这种方法说,我们最初测试的是集成测试而不是单元测试。因此,我们拆分我们的方法,提取小的“工作单元”,这些“工作单元”接收所有与另一个模块的相互依赖作为参数。这个参数可以(希望)很容易地模拟出来。这种方法的主要缺点是您不会测试所有代码,而只能说是“焦点”。您需要单独进行集成测试(通常由 QA 团队)。

于 2012-01-20T14:04:20.527 回答
1

我想知道您是否无法通过更改测试方法来解决问题。

单元测试依赖于其他模块的模块,因此依赖于其他模块的测试数据不是真正的单元测试!

如果您要为被测模块的所有依赖项注入一个模拟,这样您就可以完全隔离地对其进行测试。然后你不需要设置一个完整的环境,每个依赖模块都有它需要的数据,你只需要为你实际测试的模块设置数据。

如果您想象一个金字塔,那么基础将是您的单元测试,在此之上您有功能测试,在顶部您有一些场景测试(或者正如谷歌所说的那样,小型、中型和大型测试)。

您将拥有大量可以测试每个代码路径的单元测试,因为模拟的依赖项是完全可配置的。然后,您可以信任您的各个部分,您的功能和场景测试唯一要做的就是测试每个模块是否正确连接到其他模块。

这意味着您的模块测试数据并非由所有测试共享,而仅由组合在一起的少数测试共享。

cwash 提到的构建器模式肯定会对您的功能测试有所帮助。我们正在使用一个 .NET Builder,它被配置为构建一个完整的对象树并为每个属性生成默认值,因此当我们将其保存到数据库时,所有需要的数据都存在。

于 2012-01-20T09:27:36.220 回答