2012-01-12 64 views
10

如何管理用于测试的虚拟数据?让他们与他们各自的实体?在一个单独的测试项目中?使用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将知道约B的数据模型。

选项B)

模块A某提供了虚拟对象src/main/java../entities/dummy允许,让他们同时一个不知道的虚拟数据

选项c)

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

选项d)

使用mock框架,并根据需要手动分配所需的字段为每个测试。这里的问题是我们实体中的大多数字段都是不可空的,因此需要调用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,但不会被打包。

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

(在顺序/ SRC /测试/ 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... 
    } 
} 

问题em是,现在我们必须更新person.MockPersonorder.MockPerson,只要Person被更改。

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

回答

3

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

什么我做usaully(和AFAIKT在Java中,这是最佳实践)是尝试使用测试数据生成器模式,在他Test Data Builders后由Nat Pryce描述。

如果你觉得这是有点相关,检查这些了:

+0

嘿cwash!感谢您的工厂指针。我记得在rails教程中使用它);这可能是解决方案,我必须进一步检查它。 – Pete 2012-01-16 08:04:04

+0

@Pete - 酷,你可以留下一个笔记/更新让我们知道你的决定? – cwash 2012-01-17 01:05:25

+0

因此,看起来我们可以将它用作中央虚拟数据提供者。但最终它只是选项a),这意味着一些外部项目将包含所有必需的数据生成器。仍然想知道这是否是最好的选择。希望对此有更多的意见。 – Pete 2012-01-19 11:18:34

1

我在想如果你不能通过改变你的测试方法来解决你的问题。

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

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

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

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

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

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

+0

感谢您的回答。你能否提供一些伪代码以便更好地理解?至少在单元测试部分。我们还没有进行功能和场景测试。据我了解你,我们正在做你刚刚说的话。每个模块都有Mock类,提供可配置的数据并注入测试套件。然后每个测试调用模拟对象来创建所需的数据。我将提供一些源代码和更多信息来更详细地描述我们的方法和关注点,以便您可以检查这是否是您正在讨论的内容。 – Pete 2012-01-20 10:01:42

+0

@Pete我不是Java开发人员,所以你可以解释一下你的意思:'问题是,现在我们必须更新person.MockPerson和order.MockPerson,只要Person被更改。'你有什么改变?你不只是嘲笑那些重要的属性和方法吗? – 2012-01-20 10:37:57

+0

也许我的问题总的来说太长了,所以很难挑出必要的细节。对不起......我描述了我们必须模拟所有字段,因为大多数字段都是'nullable = false'。所以,如果我在'Person'实体中添加一个新列,我将不得不在每个模块中将模拟添加到所有的MockPerson。 – Pete 2012-01-20 11:06:43

3

那么,我仔细阅读了迄今为止所有的评估,这是一个非常好的问题。我看到以下方法解决问题:

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

第一个选项非常简单,有很多缺点,有人必须重现一次,当单元测试“搞砸”时,如果数据模块发生变化,有人必须引入相应的测试数据的变化,大量的维护开销。并不是说第一手数据的生成可能会很棘手。请参阅第二个选项。

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

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

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

+0

嘿!感谢您的意见。特别是第四种选择很有趣。你是对的,我们所做的并不是真正的单元测试,而是集成测试,因为我们从较低的模块调用管理器函数,以便在较高的模块的单元测试中创建对象,从而隐含地测试一些较低模块的功能。我意识到这使测试不是严格独立的。我想我们认为:哦,那么它只会增加所有模块的测试密度。我认为我应该阅读单元测试与集成测试的有用性...... – Pete 2012-01-20 14:57:35

+1

@Pete就是这一点。你需要确保你可以独立地测试你的函数。嘲笑每一个依赖项并使用Builder生成测试数据。同时要确保你记住德米特法则(当你只需要地址时不要传递整个客户),因为这会使你的代码更好地测试。 – 2012-01-20 16:25:57