如何TDD返回正确的结果


12

我正在开始一个新项目,并非常非常努力地使用TDD来驱动设计。我已经努力了很多年,终于获得批准,可以花更多的时间在这个项目上使用它,同时我会学习如何正确地做。

这是一个新模块,可以绑定到现有系统中。当前,所有数据访问都通过Web服务进行,Web服务在大多数情况下只是数据库存储过程的一个精简包装。

一个要求是,对于给定的商店,我将退回所有被认为对该应用程序有效的采购订单。如果PO的发货日期在商店开业日期的指定范围内(对于新商店),则认为该PO有效。

现在,我不能在应用程序代码中放入此逻辑,因为鉴于上述限制,我不会带回一百万个PO,只是为了使适用于该商店的十二个PO可以应用到该商店。

我当时在想,我可以将日期范围传递给GetValidPOs proc,并使用这些值返回有效的PO。但是,如果我们在被认为是有效的采购订单中添加了另一个要求,该怎么办?

以及如何测试并验证其是否正常运行?我们没有使用ORM,并且不太可能发生。而且我无法在测试中调用数据库。

我被卡住了。

我的另一个想法是,有一些模拟返回有效数据,其他模拟返回一些不良数据,并让本地存储库在发生不良数据时引发异常,并测试如果GetValidPOs proc返回无效数据,则引发异常测试中使用的模拟)。

这有意义吗?或者,还有更好的方法?

更新: 看起来我可以使用EF。现在,我只需要弄清楚如何使用它,并使它可测试,同时仍然能够依赖存储过程,以及使数据分散在多个数据库中的困难。


出于好奇,为什么不能使用简单的SQL语句仅选择有效的PO?(此问题或答案并不意味着有解决方案。)
Scarridge

Answers:


7

这是TDD时代存储过程的主要缺点。即使在现在,它们也有一些实际的优势,但是从定义上讲,任何执行存储过程的测试都不是单元测试。充其量是一次集成测试。

假设体系结构无法更改为使用ORM,通常的解决方案是不将这些测试放入单元测试套件中;而是将这些测试放入单元测试套件中。而是将测试放在集成套件中。只要您想验证测试是否可以运行,就仍然可以运行测试,但是由于设置测试(使用适当的测试数据初始化数据库)的内在成本很高,而且涉及到构建机器人的单元测试代理的资源,有权访问,不应将其包含在单元测试套件中。

通过将无法进行单元测试的任何内容(ADO.NET类)抽象到可以模拟的DAO类中,您仍然可以对需要数据的代码进行单元测试。然后,您可以通过使用代码来验证是否进行了预期的调用,并重现了现实世界的行为(例如未找到结果),从而可以测试各种用例。但是,实际的SqlCommand设置来调用存储的proc几乎是您可以单元测试的最后一件事,方法是从命令执行中切断命令创建并模拟命令执行器。如果这听起来像是关注点的分离,那就可以了;请记住,“除了有太多的间接层之外,没有其他层无法解决的问题”。在某些时候,您必须说“足够;我只是无法对此进行单元测试,我们

其他选项:

  • 使用“短期” DBMS实例(如SQLite)测试存储的proc。使用ORM时通常更容易做到这一点,但是可以在“内存中”(或使用测试套件附带的预设数据库文件)进行测试。仍然不是单元测试,但是它可以高度隔离地运行(DBMS是正在运行的过程的一部分,而不是您可能远程连接到的位于其他人相互冲突的测试套件中间的部分)。不利之处在于,在生产过程中可能会发生对存储proc的更改,而测试不会反映出更改,因此您必须遵守纪律,以确保首先在测试环境中进行更改。

  • 考虑升级到ORM。带有Linq提供程序的ORM(几乎所有常用的提供程序都具有一个)可以使您将查询定义为Linq语句。然后可以将该语句提供给具有存储在其中的测试数据的模拟存储库,以将其应用于该存储库。因此,您甚至不需要接触数据库就可以验证查询是否正确(您仍应在集成环境中运行查询,以测试Linq提供程序可以正确地消化查询)。


2
-1,因为TDD!=单元测试。进行TDD时可以很好地包含集成级别的测试。
史蒂文·劳

单元测试是测试驱动开发的子集。在测试驱动的开发中,您将创建系统的框架,然后在该系统上运行单元测试,集成测试和功能测试。您的集成,单元​​或验收测试失败,然后使它们通过并编写进一步的测试。
CodeART 2012年

1
我都明白,你们俩。我在哪里说必须进行集成测试意味着您不能TDD?我的观点是,不能孤立地测试存储过程,这是您想要对尽可能多的代码库进行的操作。相反,测试SP需要更复杂,运行时间更长的集成测试。虽然它们仍然比手动测试要好,但是大量集成的测试套件可能要花几个小时才能运行,并且可能对CI的工作产生不利影响。
KeithS 2012年

SP测试通常还需要在测试数据库中设置特定的数据集。与实际执行的代码相比,使数据库处于适当状态以达到预期结果的代码通常具有更高的LoC值和更长的运行时间。这进一步增加了测试套件的时间复杂性,并且通常必须为每个单独的测试重复设置(每个SP可能应该重复多个测试,以测试是否满足查询中的每个功能要求)。
KeithS 2012年

存储过程可以单独进行测试。还将如何验证它们?对Transact SQL有tSQLt(tsqlt.org
凯文·克莱因

4

我的建议是分而治之。暂时不要考虑数据库和持久性,而应专注于测试存储库或数据访问对象的虚假实现。

现在,我不能在应用程序代码中放入此逻辑,因为鉴于上述限制,我不会带回一百万个PO,只是为了使适用于该商店的十二个PO可以应用到该商店。

我会模拟返回采购订单的存储库。用二十个奇怪的采购订单创建一个模拟。

我当时在想,我可以将日期范围传递给GetValidPOs proc,并使用这些值返回有效的PO。但是,如果我们在被认为是有效的采购订单中添加了另一个要求,该怎么办?

存根对GetValidPOs的调用,以便它调用您的模拟而不是数据库过程。

以及如何测试并验证其是否正常运行?我们没有使用ORM,并且不太可能发生。而且我无法在测试中调用数据库。

您需要进行单元测试,以确保从模拟返回正确的数据。

您还需要进行集成测试,以确保从数据库返回正确的数据。集成测试将需要一些配置和清理。例如,在运行集成测试之前,通过运行脚本为数据库添加种子。验证脚本是否有效。通过调用您的存储过程来查询数据库。验证结果是否正确。清理数据库。

我的另一个想法是,有一些模拟返回有效数据,其他模拟返回一些不良数据,并让本地存储库在发生不良数据时引发异常,并测试如果GetValidPOs proc返回无效数据,则引发异常测试中使用的模拟)。

正如我已经说过的,您需要一个至少返回一些可以查询的数据的模拟。

查询数据时,您要确保系统可以正常处理异常。因此,您可以模拟行为,以便在某些情况下引发异常。然后,编写测试以确保您的系统可以正常处理这些异常。


那就是我想做的。由于我们的数据访问不利于使用ORM,因此很难编写与模拟操作相同的真实实现。我需要的大多数数据都在多个系统中,并且应该可以通过Web服务进行访问,即使在更新时也是如此。
CaffGeek

0

就像单元测试Java或Javascript意味着使用Java的Java语言编写单元测试,以及使用Javascript进行单元测试Java一样,编写自动测试来驱动您编写存储过程也意味着您要查找的单元测试库基于存储程序。

换句话说,使用存储过程来测试存储过程是因为:

  • 由于您正在使用过程语言进行开发,因此您应该具有使用过程语言编写测试的技能
  • 用您的程序语言编写测试将提高您在程序语言中的技能,从而帮助您开发产品
  • 您将可以直接访问数据库提供的所有工具,并且也可以使用这些工具来保持单元测试尽可能简单
  • 与您要测试的过程存储在同一数据库中的单元测试将很快(与速度一样是单元测试中最接近的东西),因为您不会跨越系统边界

就像OO局域网中的TDD一样,您希望单元测试仅设置一行左右的数据来测试该过程所需的内容(极简主义,只有您的简单测试所需要的)。这样的结果是您将对每个存储过程进行几个简单的单元测试。这些简单的测试比依赖于无法轻松映射回测试实际需求的大型数据集的复杂测试要容易维护。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.