持久化复杂的测试数据

8
我们正在使用构建器模式来生成测试数据。这些领域对象之间存在关系。我们的功能测试需要将这些对象持久化。
考虑以下模型:

domain model

如果我想要一个纯粹的C实例,我会执行。
如果我想要它被持久化,我会执行。
如果我想要一个已知B的C实例,我会执行。
我的问题是,如果我想要持久化一个C,它应该持久化它的B吗?还是应该事先持久化?如果我想要一个合理的默认B怎么办?如果我想要持久化D,它应该持久化所有A、B、C吗?
当然,实际系统要复杂得多(有时还存在循环引用)。我正在寻找持久化复杂测试数据的最佳实践。
编辑:看起来我遇到了语言障碍,我的母语不是英语,所以对于不清楚的地方我很抱歉。这里有更多信息:
- 我试图编写覆盖率测试,而不是单元测试(因此我不会模拟任何内容)。 - 我尝试测试的软件片段在数据库被填充到一定程度时可以工作(它不使用所有实体)。

附言:请随时索取更多信息,因为我一直在努力寻找可能的最佳实践。我想到的最接近的方法是:

  1. 在构建实体时跟踪已经显式设置的内容。
  2. 假设已经显式设置的实体已经持久化,不要再次持久化。
  3. 将其他所有内容(使用它们自己的持久化器)持久化。

这个方法可以工作,但我的直觉告诉我,我正在做错什么,因为测试代码中会涉及逻辑,如果没有测试,处理起来将非常复杂。

编辑2:我将尝试让自己更清楚。当我编写/运行单元测试和一些集成测试时,我没有问题,因为测试数据没有持久化,它存在于内存中。

但是,当我尝试持久化我的测试数据时,Hibernate 不允许我保存一个没有关联的实体。

我该如何解决这个问题?

6个回答

3
您应该更详细地描述您的测试设置。特别是,为什么您的功能测试需要这些对象被持久化?您是在测试实际的持久化操作吗?还是这只是运行测试的副作用?您想要在测试中加载已持久化的对象吗?
我的问题是,如果我想持久化C,它是否应该先持久化B?
这取决于您首先为何持久化。如果您正在集成测试持久化层,则应使用应用程序本身使用的逻辑。如果这只是测试的副作用,您可能需要模拟持久化层等...

想象一下这些数据已经存在于数据库中。另一个进程(我正在测试)正在读取这些数据。但有时候B是相关的,我想在创建和持久化B时将其显示在测试中;但有时它们不相关,我试图将它们隐藏在构建器后面。 - nimcap
这对我来说没有意义。如果数据已经在数据库中,为什么(以及何时)需要将其持久化?而你所说的“我想让它在测试中可见”,是什么意思? - sleske

1

你需要更好地定义你的领域级联。如果你不能测试它,你怎么能期望它在实际应用中表现良好呢?

例如:

A -> B:这个关系的所有者是谁?你想将B添加到A中,还是反过来?这可以是一个实现细节,你可以同时拥有B.SetParent(A)和A.Children.Add(B),并且在A.Children.Add(B)的情况下将B的父级设置为A(同样也是反过来)。如果你做了什么会发生:

A a1 = new A();
A a2 = new A();
B b = new B();
a1.Children.Add(b);
b.SetParent(a);

在这里你需要自己做决定。没有完美的解决方案,所以个人喜好和应用程序的一致性才是最重要的。

使用ORM比起使用纯SQL(或任何其他数据源,如XML或自定义数据源)更容易出现约束问题,但如果你使用纯SQL,也需要考虑这些问题。

很抱歉,我不能给你一个确定的答案,但在我看来,你需要考虑一些制约因素,这些因素(我猜)你还没有考虑到。

个人而言,在处理DAL时使用NHibernate时,我喜欢仓储模式。我让我的存储库实现IDisposable,并让它们各自获得一个会话。这样,你就可以将“工作单元”模式融入你的设计中。

祝你好运 :)


1

我按主题分开了你的答案。

我的问题是,如果我想要持久化一个C,它应该持久化它的B吗?如果我想要持久化一个D呢?它应该持久化所有的A、B、C吗?

这完全取决于您选择强制执行的领域约束。例如,C是否是实体,B是值对象?换句话说,C是否具有唯一的身份和自己的生命周期?B是否主要由其值标识,并且其生命周期与其父级C紧密耦合?

询问这些类型的问题应该有助于指导您在何时以及由谁持久化什么。

例如,如果实体C和B仅共享关系,则您可能决定将它们独立持久化,因为每个实体都可能有自己的有意义的生命周期和标识。如果B是值对象,则您可能选择让其父实体C控制其生命周期,包括创建/检索/更新/删除对象。这很可能包括C持久化B。
“或者应该事先持久化吗?”
要回答这个问题,您需要映射对象依赖项。当将对象图持久化到RDBMS时,这些依赖关系通常由外键约束表示。如果C没有B的引用就无法运行,则您可能希望在事务中同时持久化它们,其中B首先完成以符合数据库的外键约束。按照上述思路,如果B是C的子实体或值对象,则甚至可以让C负责持久化B。
“那如果我想要一个合理的默认B呢?”

创建B实例可以委托给B-工厂。无论您将此工厂逻辑实现为类(非实例)方法、构造函数,还是将其作为自己的单元分离出来都没有关系。重点是您有一个地方用于创建和配置新的B。在这个地方,您将对新实例化对象进行默认配置。

涵盖这些问题的优秀资源是Eric Evans的领域驱动设计


我无法决定接受哪个,另一个先到了……所以我很抱歉,我希望有一种方法可以接受多个答案。 - nimcap

1

我不确定我很好地理解了你试图解决的问题,但是……使用类似于XStream或Google的Protocol Buffers这样的东西将整个图形序列化为XML如何?


Google协议缓冲区已添加为收藏。 - Arthur Ronald

0
  • 你的测试结果是什么?
  • 听起来你正在测试一个遗留应用程序?
  • 所以你正在采取已经写入代码库中的功能,并尝试创建覆盖测试?

请给我们一些更多的反馈。


0
据我所见,问题出在您的域(如您所绘制的那样)。据我了解,C与B之间存在多对一的关系,并且数据库通过非空外键字段来强制执行它。另一方面,从问题中的代码中,我可以理解到代码中没有强制执行恰好一个规则,而在C实例中引用B实例的成员可能为空。据我所知,域模型应始终在代码和运行时正确,因此如果该规则已在代码中强制执行(例如,在C build()方法中要求B引用),则您不会遇到任何持久性问题-您可以只需持久化所有内容。
其他更脏的解决方案是在测试之前以编程方式删除所有干扰测试的DB约束,并在测试后恢复它们。当然,这将使DB对于与测试并行运行的任何其他内容完全无法使用,但是可以通过仅针对测试使用SQLite或SQL Server Compact Edition等集成的DB来解决此问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接