模块化应用程序堆栈中的虚拟数据和单元测试策略

时间:2012-01-12 14:04:07

标签: java unit-testing testing maven modular

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

我们有一个应用程序堆栈,其中包含多个模块,具体取决于另一个模块。每个模块都有自己的测试,需要运行虚拟数据。

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

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

是否有最佳实践方法或所有解决方案都妥协?


更多详情

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

一个模块:

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 上运行安装,它将不包含由 T 引入的引用,尤其是不是 B ?然而 A 会知道 B 的数据模型。

选项b)

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

选项c)

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

选项d)

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

我们不想要的内容

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


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


修改

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

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

(在 person / 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课程不会打包。

同样适用于产品测试:

(在 product / src / test / java :)

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

MockProduct是必需的,但不会打包。

现在订单测试需要MockPersonMockProduct,所以现在我们需要同时创建MockOrder来测试Order

(在 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.MockPersonPerson

使用jar发布Mocks是不是更好,这样每个其他具有依赖性的测试都可以调用Mock.mock并获得一个很好的安装对象?或者这是黑暗的一面 - 简单的方法?

3 个答案:

答案 0 :(得分:3)

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

我已完成的工作(以及AFAIKT in Java这是最佳实践)尝试使用Test Data Builder模式,如Nat Pryce所述{{3}发帖。

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

答案 1 :(得分:3)

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

  1. 设置(静态)测试数据库;
  2. 每个测试都有自己的设置数据,可以在运行单元测试之前创建(动态)测试数据;
  3. 使用虚拟或模拟对象。所有模块都知道所有虚拟对象,这种方式没有重复;
  4. 缩小单元测试的范围;
  5. 第一个选项非常直接并且有许多缺点,有人必须重现它一次,当单元测试“弄乱”时,如果数据模块有变化,有人必须引入相应的更改测试数据,大量维护开销。并不是说第一手产生这些数据可能很棘手。见第二个选项。

    第二个选项,您编写测试代码,在测试之前调用一些创建实体的“核心”业务方法。理想情况下,您的测试代码应独立于生产代码,但在这种情况下,您最终会得到重复的代码,您应该支持两次。有时,最好分割你的生产业务方法,以便为你的单元测试提供入口点(我将这些方法设为私有,并使用Reflection来调用它们,还需要对方法进行一些评论,现在重构有点棘手) 。主要的缺点是,如果你必须改变你的“核心”业务方法,它会突然影响你所有的单元测试而你无法测试。因此,开发人员应该意识到这一点,而不是让部分提交到“核心”业务方法,除非它们有效。此外,如果该区域有任何变化,您应该记住“它将如何影响我的单元测试”。有时候,也不可能动态地再现所有需要的数据(通常,这是因为第三方API,例如,你用其自己的数据库调用另一个应用程序,你需要使用某些键。这个键(带有通过第三方应用程序手动创建关联数据。在这种情况下,应该静态创建此数据和仅此数据。例如,您创建的10000个密钥从300000开始。

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

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

答案 2 :(得分:1)

我想知道你是否通过改变测试方法无法解决问题。

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

如果您要为测试模块的所有依赖项注入模拟,那么您可以完全隔离测试它。然后,您不需要设置一个完整的环境,其中每个依赖模块都具有所需的数据,您只需为实际测试的模块设置数据。

如果你想象一个金字塔,那么基础将是你的单元测试,高于你有功能测试,在顶部你有一些场景测试(或谷歌称之为小,中,大测试)。 p>

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

这意味着您的模块测试数据不会被所有测试共享,只会被一些组合在一起的测试数据共享。

cwash提到的Builder Pattern肯定会对你的功能测试有所帮助。 我们使用的.NET Builder配置为构建完整的对象树并为每个属性生成默认值,因此当我们将其保存到数据库时,所有必需的数据都会出现。