unit testing数据库驱动应用程序的最佳策略是什么?

我使用了很多由后端数据库驱动的Web应用程序。 通常,有一个独立于业务和表示逻辑的ORM层。 这使得对业务逻辑进行unit testing相当简单; 事情可以在离散模块中实现,并且testing所需的任何数据都可以通过对象模仿来伪造。

但是testingORM和数据库本身一直充满着问题和妥协。

多年来,我尝试了一些策略,其中没有一个能完全令我满意。

  • 用已知数据加载testing数据库。 针对ORM运行testing,并确认正确的数据返回。 这里的缺点是您的testing数据库必须跟上应用程序数据库中的任何模式更改,并且可能会不同步。 它也依赖于人造的数据,并且可能不会暴露由于愚蠢的用户input而发生的错误。 最后,如果testing数据库很小,则不会像缺less索引那样显示效率低下。 (好吧,最后一个unit testing应该不是什么用处,但是并没有受到影响。)

  • 加载生产数据库的副本并进行testing。 这里的问题是,你可能不知道什么是在生产数据库在任何时候; 如果数据随时间变化,您的testing可能需要重写。

有人指出,这两种策略都依赖于具体的数据,unit testing只能testingfunction。 为此,我已经看到了build议:

  • 使用模拟数据库服务器,并检查ORM是否正在发送正确的查询来响应给定的方法调用。

你用什么策略来testing数据库驱动的应用程序? 最适合你的是什么?

实际上,我已经使用了第一种方法,取得了相当大的成功,但以一种稍微不同的方式解决了一些问题:

  1. 保留整个模式和脚本以在源代码控制中创build它,以便任何人都可以在签出后创build当前数据库模式。 另外,将样本数据保存在由构build过程的一部分加载的数据文件中。 当您发现导致错误的数据时,请将其添加到您的示例数据中,以检查是否不会再出现错误。

  2. 使用持续集成服务器来构build数据库模式,加载示例数据并运行testing。 这就是我们如何保持我们的testing数据库同步(在每次testing运行时重build它)。 虽然这要求CI服务器可以访问和拥有自己的专用数据库实例,但是我认为每天创build3次我们的db模式可以极大地帮助发现可能在交付之前才发现的错误(如果不晚于)。 我不能说我在每次提交之前重build模式。 有人吗? 采用这种方法,你不必(也许我们应该,但是如果有人忘记了,这不是什么大问题)。

  3. 对于我的组,用户input是在应用程序级别(而不是db)完成的,所以这是通过标准的unit testing来testing的。

加载生产数据库复制:
这是我上一份工作使用的方法。 这是几个问题的巨大痛苦原因:

  1. 该副本将从生产版本中过期
  2. 对副本的模式进行更改,不会传播到生产系统。 在这一点上,我们会有不同的模式。 不好玩。

嘲笑数据库服务器:
我目前的工作也是这样做的。 每次提交之后,我们都会针对注入了模拟db访问器的应用程序代码执行unit testing。 然后每天三次执行上面描述的全部数据库构build。 我绝对推荐这两种方法。

由于以下原因,我总是对内存数据库(HSQLDB或Derby)运行testing:

  • 它使你想到哪些数据保存在你的testing数据库中,以及为什么。 只要将您的生产数据库拖入testing系统,就意味着“我不知道我在做什么或为什么,如果有什么事情发生,那不是我的! ;)
  • 它确保可以在新的地方轻松地重新创build数据库(例如,当我们需要复制生产中的错误时)
  • 它极大地帮助了DDL文件的质量。

一旦testing开始,在大多数testing之后,内存数据库将加载新的数据,我调用ROLLBACK来保持稳定。 始终保持testing数据库中的数据稳定! 如果数据一直在变化,则无法进行testing。

数据从SQL,模板数据库或转储/备份中加载。 我喜欢转储,如果他们是在一个可读的格式,因为我可以把他们在VCS。 如果这不起作用,我使用CSV文件或XML。 如果我必须加载大量的数据…我不知道。 你永远不需要加载大量的数据:)不适用于unit testing。 性能testing是另一个问题,适用不同的规则。

我一直在问这个问题很长一段时间,但是我觉得没有这个东西。

我目前所做的就是嘲笑DAO对象,并在内存中保存一组代表可以存在于数据库中的有趣数据的对象。

我用这种方法看到的主要问题是,你只覆盖了与你的DAO层交互的代码,但从来没有testing过DAO本身,而且根据我的经验,我发现在这个层上也发生了很多错误。 我还保留了一些针对数据库运行的unit testing(为了使用TDD或本地快速testing),但是这些testing永远不会在我的持续集成服务器上运行,因为我们没有为此保留一个数据库,认为在CI服务器上运行的testing应该是独立的。

另一种我觉得非常有趣的方法,但并不总是值得的,因为它有点花时间,所以在embedded式数据库中创build相同的模式,这些数据库只是在unit testing中运行。

尽pipe这种方法毫无疑问可以提高覆盖范围,但还是有一些缺点,因为必须尽可能地接近ANSI SQL,才能使其与当前的DBMS和embedded式替代品一起工作。

不pipe你认为什么与代码更相关,那里有一些可以使它更容易的项目,比如DbUnit 。

即使有一些工具可以让你以某种方式模拟你的数据库(例如jOOQ的MockConnection ,在这个答案中可以看到 – 免责声明,我为JOOQ的供应商工作),我build议不要用更大的数据库来模拟复杂的查询。

即使您只是想集成testing您的ORM,也要注意ORM向您的数据库发出一系列非常复杂的查询,这可能会有所不同

  • 句法
  • 复杂
  • 命令(!)

嘲笑所有这些以产生明智的虚拟数据是相当困难的,除非你真的在你的模拟内部构build一个小数据库来解释传输的SQL语句。 话虽如此,请使用众所周知的集成testing数据库,您可以轻松地使用众所周知的数据进行重置,您可以运行集成testing。

我使用第一个(对testing数据库运行代码)。 我看到你用这种方法提出的唯一的实质性问题是模式不同步的可能性,我通过在数据库中保留一个版本号,并通过一个应用每个版本增量更改的脚本来更改所有模式更改。

我还首先对我的testing环境进行了所有的更改(包括到数据库模式),所以它最终是相反的:在所有testing通过之后,将模式更新应用到生产主机。 我还在开发系统上保存了一对单独的testing和应用程序数据库,这样我就可以在接触实际生产框之前validation数据库升级是否正常工作。

对于基于JDBC的项目(直接或间接的,例如JPA,EJB,…),您可以模拟整个数据库(在这种情况下最好在真实的RDBMS上使用testing数据库),但只能在JDBC级别。

好处就是抽象,因为JDBC数据(结果集,更新计数,警告…)是相同的,无论是后端:你的prod数据库,testing数据库,或只是为每个testing提供的一些模型数据案件。

通过JDBC连接模拟每个案例,不需要pipe理testing数据库(清理,只有一次testing,重新加载夹具,…)。 每个样机连接都是隔离的,不需要清理。 在每个testing用例中只提供了最less的所需固件来模拟JDBC交换,这有助于避免pipe理整个testing数据库的复杂性。

Acolyte框架包括一个JDBC驱动程序和用于这种模型的工具: http : //acolyte.eu.org 。