14

人们如何对他们的业务应用程序进行单元测试?我已经看到了很多带有“易于测试”示例的单元测试示例。前任。一个计算器。人们如何对数据密集型应用程序进行单元测试?你是如何把你的样本数据放在一起的?在许多情况下,一个测试的数据可能对另一项测试根本不起作用,这使得只有一个测试数据库变得困难?

测试代码的数据访问部分相当简单。它正在测试所有适用于似乎难以测试的数据的方法。例如,想象一个发布过程,其中有大量数据访问以确定发布的内容,调整数字等。有许多中间步骤发生(并且需要测试)以及之后的测试,以确保发布是成功的。其中一些步骤实际上可能是存储过程。

过去我曾尝试将测试数据插入测试数据库,然后运行测试,但老实说,编写这种代码非常痛苦(而且容易出错)。我也尝试过预先构建一个测试数据库并回滚更改。这行得通,但在许多地方你也不能轻易做到这一点(很多人会说这是集成测试;就这样吧,我仍然需要能够以某种方式对其进行测试)。

如果答案是没有一种很好的方法来处理这个问题并且它目前只是有点糟糕,那么知道这也是有用的。

任何想法、想法、建议或提示都将受到赞赏。

4

6 回答 6

6

我的自动化功能测试通常遵循以下两种模式之一:

  • 数据库连接测试
  • 模拟持久层测试

数据库连接测试

当我有连接到数据库的自动化测试时,我通常会制作一个测试数据库模板,其中包含足够的数据用于所有测试。运行自动化测试时,会为每个测试从模板生成一个新的测试数据库。测试数据库必须不断地重新生成,因为测试会经常改变数据。随着测试的添加,我通常会将更多数据附加到测试数据库模板中。

这种测试方法有一些很好的优点。明显的优势是测试还可以锻炼您的模式。另一个优点是在设置初始测试后,大多数新测试将能够重新使用现有的测试数据。这使得添加更多测试变得容易。

缺点是测试数据库将变得笨拙。因为数据通常会一次添加一个测试,所以会不一致,甚至可能不切实际。当数据库架构发生重大变化时,您最终还会诅咒设置测试数据库的人(这对我来说通常意味着我最终会诅咒自己)。

如果不能随意生成新的测试数据库,这种测试方式显然是行不通的。

模拟持久层测试

对于此模式,您创建与测试用例一起存在的模拟对象。这些模拟对象拦截对数据库的调用,以便您可以以编程方式提供适当的结果。基本上,当您测试的代码调用该findCustomerByName()方法时,会调用您的模拟对象而不是持久层。

使用模拟对象测试的好处是您可以变得非常具体。通常,在没有模拟对象的自动化测试中,有些执行路径是您根本无法达到的。它们还使您免于维护庞大的、单一的测试数据集。

另一个好处是没有外部依赖。因为模拟对象模拟持久层,所以您的测试不再依赖于数据库。这通常是选择哪种模式时的决定因素。在处理遗留数据库系统或具有严格许可条款的数据库时,模拟对象似乎更受关注。

模拟对象的缺点是它们通常会导致大量额外的测试代码。这并不可怕,因为几乎任何数量的测试代码在按运行测试的次数摊销时都很便宜,但是测试代码多于生产代码可能会很烦人。

于 2008-09-02T01:26:55.253 回答
2

这取决于您要测试的内容。如果您正在测试一个业务逻辑组件——那么它的数据来自哪里并不重要,您可能会使用模拟或手动存根类来模拟组件在野外调用的数据访问例程。我唯一一次搞砸数据访问是在我实际测试数据访问组件本身的时候。

即使这样,我也倾向于在 TestFixtureSetUp 方法中打开一个数据库事务(显然这取决于您可能使用的单元测试框架)并在测试套件 TestFixtureTeardown 结束时回滚事务。

于 2008-09-01T23:42:03.507 回答
2

模拟框架使您能够测试您的业务对象。数据驱动测试通常最终变得更像是一种集成测试而不是单元测试,它们还承担着管理测试执行前后的数据存储状态以及连接和执行查询所花费的时间的负担。

一般来说,我会避免进行从业务对象触及数据库的单元测试。至于测试您的数据库,您需要不同的策略。

话虽这么说,您永远无法完全摆脱数据驱动测试,只限制实际需要调用后端系统的测试数量。

于 2008-09-01T23:48:52.117 回答
2

当我尝试使用回滚解决方案来处理这些集成测试时,我必须支持@Phil Bennett 的评论。

我在这里有一篇关于集成测试您的数据访问层的非常详细的帖子

我不仅展示了示例数据访问类、基类和示例 DB 事务夹具类,还展示了带有示例数据的完整 CRUD 集成测试。使用这种方法,您不需要多个测试数据库,因为您可以控制每个测试的数据,并且在测试完成后,事务都回滚,因此您的数据库是干净的。

关于在您的应用程序中对业务逻辑进行单元测试,我还将支持@Phil 和@Mark 的评论,因为如果您模拟出您的业务对象所具有的所有依赖项,那么一次测试您的应用程序逻辑一个实体变得非常简单;)

编辑:那么您是否正在寻找一个巨大的集成测试来验证所有内容,从逻辑预数据库/存储过程运行 w/逻辑,最后在返回的路上进行验证?如果是这样,您可以将其分为两个步骤:

  • 1 - 对数据推送到数据访问代码之前发生的逻辑进行单元测试。例如,如果你有一些代码根据一些属性计算一些数字——编写一个测试,只检查这个 1 函数的逻辑是否符合你的要求。模拟出对数据访问类的任何依赖,以便您可以在单独测试应用程序逻辑时忽略它。

  • 2 - 集成测试一旦您获取操作数据(来自我们单元测试的先前方法)并调用适当的存储过程时发生的逻辑。在特定于数据的测试类中执行此操作,以便在完成后回滚。存储过程运行后,对数据库进行查询以获取您的对象,因为我们已经对数据执行了一些逻辑并验证它是否具有您期望的值(存储后过程逻辑 /etc )

如果您需要数据库中的一个条目来运行存储过程,只需在运行包含您的逻辑的存储过程之前插入该数据。例如,如果您有需要测试的产品,它可能需要插入供应商和类别条目,因此在插入产品之前,请为供应商和类别快速而肮脏地插入,以便您的产品插入按计划工作。

于 2008-09-01T23:54:30.933 回答
1

听起来您可能正在测试基于消息的系统,或具有高度参数化接口的系统,其中输入数据有大量排列。

一般来说,标准 unti 测试的所有规则仍然成立:

  • 尝试使被测试的单元尽可能小和离散。
  • 尝试使测试独立。
  • 分解代码以解耦依赖关系。
  • 使用模拟和存根替换依赖项(如数据访问)

完成此操作后,您将从测试中消除很多复杂性,并有望揭示出好的单元测试集,并简化示例数据。

然后编译样本数据以进行仍然需要复杂输入数据的测试的一个很好的方法是正交测试,或参见此处

我已经使用这种方法为 WCF 和 BizTalk 解决方案生成测试计划,其中输入消息的排列可以创建多个可能的执行路径。

于 2008-09-01T23:44:37.743 回答
0

对于相同逻辑上的许多不同运行,但使用不同的数据,您可以使用 CSV,输入任意多的列,输出的最后一列等。

于 2008-09-01T23:25:33.640 回答