模块化应用程序堆栈中的虚拟数据和unit testing策略

如何管理用于测试的虚拟数据? 将它们与各自的实体保持一致? 在一个单独的测试项目中? 使用外部资源的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上运行install,它是否包含由T引入的引用,尤其不是B ? 然而A会知道B的数据模型。

选项b)

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

选项c)

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

选项d)

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

我们不想要的

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


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


编辑

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

所以为简单起见,我们有三个模块: 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类不会打包。

这同样适用于产品测试:

(在product / src / test / java :)中

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

需要MockProduct但不会打包。

现在订单测试需要MockPersonMockProduct ,所以现在我们需要创建两个以及MockOrder来测试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被更改时更新person.MockPersonorder.MockPerson

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

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

我已经完成了(以及Java中的AFAIKT,这是最佳实践 )尝试使用测试数据生成器模式,如Nat Pryce在他的Test Data Builderspost中所描述的那样。

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

  • 像Factory Factory这样的框架是否存在?
  • make-it-easy ,Nat的框架实现了这种模式。

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

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

第一个选项非常简单并且有许多缺点,有人必须重现它一次,当unit testing“弄乱”时,如果数据模块有变化,有人必须对测试数据进行相应的更改,很多维护费用。 并不是说第一手产生这些数据可能很棘手。 见第二个选项。

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

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

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

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

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

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

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

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

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

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