我使用很多Web应用程序,这些应用程序由后端不同复杂程度的数据库驱动.通常,存在与业务和表示逻辑分离的ORM层.这使得对业务逻辑的单元测试相当简单; 事物可以在离散模块中实现,测试所需的任何数据都可以通过对象模拟来伪造.
但是测试ORM和数据库本身一直充满了问题和妥协.
多年来,我尝试了一些策略,其中没有一个完全满足我.
使用已知数据加载测试数据库.针对ORM运行测试并确认正确的数据返回.这里的缺点是您的测试数据库必须跟上应用程序数据库中的任何模式更改,并且可能会不同步.它还依赖于人工数据,并且可能不会暴露由于愚蠢的用户输入而发生的错误.最后,如果测试数据库很小,它将不会显示缺失索引等低效率.(好吧,最后一个不是真的应该使用单元测试,但它没有受到伤害.)
加载生产数据库的副本并对其进行测试.这里的问题是你可能不知道在任何给定时间生产数据库中有什么; 如果数据随时间变化,您的测试可能需要重写.
有些人指出,这两种策略都依赖于特定的数据,单元测试应该只测试功能.为此,我见过建议:
使用模拟数据库服务器,并仅检查ORM是否正在发送正确的查询以响应给定的方法调用.
您使用了哪些策略来测试数据库驱动的应用程序?什么对你有用?
我实际上已经使用了你的第一种方法取得了相当大的成功,但我认为这种解决方案可以解决一些问题:
保留整个架构和脚本,以便在源代码管理中创建它,以便任何人都可以在签出后创建当前数据库架构.此外,将样本数据保存在部分构建过程中加载的数据文件中.当您发现导致错误的数据时,请将其添加到示例数据中以检查错误是否重新出现.
使用持续集成服务器构建数据库模式,加载示例数据并运行测试.这就是我们如何使测试数据库保持同步(在每次测试运行时重建它).虽然这要求CI服务器具有对其自己的专用数据库实例的访问权和所有权,但我说每天建立3次我们的数据库模式可以极大地帮助找到可能在发送之前就找不到的错误(如果不是以后的话) ).我不能说我在每次提交之前重建架构.有人吗?通过这种方法你不必(也许我们应该,但如果有人忘记,这不是什么大问题).
对于我的组,用户输入在应用程序级别(而不是db)完成,因此通过标准单元测试进行测试.
加载生产数据库副本:
这是我上一份工作中使用的方法.这是几个问题的巨大痛苦原因:
副本将从生产版本中过时
将对副本的架构进行更改,并且不会传播到生产系统.在这一点上,我们有不同的模式.不好玩.
模拟数据库服务器:
我们也在目前的工作中这样做.在每次提交之后,我们对注入了模拟db访问器的应用程序代码执行单元测试.然后我们每天三次执行上面描述的完整db构建.我绝对推荐这两种方法.
由于以下原因,我总是针对内存数据库(HSQLDB或Derby)运行测试:
它使您可以考虑在测试数据库中保留哪些数据以及原因.将生产数据库运送到测试系统只是"我不知道我在做什么或为什么,如果有什么东西坏了,那不是我!" ;)
它确保可以在新的位置轻松地重新创建数据库(例如,当我们需要从生产中复制错误时)
它极大地帮助了DDL文件的质量.
一旦测试开始,内存数据库就会加载新数据,在大多数测试之后,我调用ROLLBACK来保持稳定.始终保持测试数据库中的数据稳定!如果数据一直在变化,则无法进行测试.
数据从SQL,模板DB或转储/备份加载.如果它们是可读格式,我更喜欢转储,因为我可以将它们放在VCS中.如果这不起作用,我使用CSV文件或XML.如果我必须加载大量数据......我没有.您永远不必加载大量数据:)不适用于单元测试.性能测试是另一个问题,适用不同的规则.
我一直在问这个问题很长一段时间,但我认为没有灵丹妙药.
我目前所做的是模拟DAO对象并保持内存表示一个良好的对象集合,这些对象代表可以存在于数据库中的有趣数据案例.
我用这种方法看到的主要问题是,你只覆盖与DAO层交互的代码,但从不测试DAO本身,根据我的经验,我发现在该层上也发生了很多错误.我还保留了一些针对数据库运行的单元测试(为了在本地使用TDD或快速测试),但这些测试永远不会在我的持续集成服务器上运行,因为我们没有为此目的保留数据库而且我认为在CI服务器上运行的测试应该是自包含的.
我觉得非常有趣的另一种方法,但并不总是值得花费一点时间,就是在单元测试中运行的嵌入式数据库上创建用于生产的相同模式.
即使毫无疑问,这种方法可以提高您的覆盖率,但也存在一些缺点,因为您必须尽可能接近ANSI SQL,以使其与您当前的DBMS和嵌入式替换相结合.
无论你认为什么与你的代码更相关,有一些项目可以使它更容易,如DbUnit.
即使有工具,让你嘲笑你的数据库在这种或那种方式(如jOOQ的MockConnection
,它可以被看作这个答案 -免责声明,我对jOOQ的供应商合作),我会建议不嘲笑复杂的大型数据库查询.
即使您只是想集成测试您的ORM,请注意ORM向您的数据库发出一系列非常复杂的查询,这些查询可能会有所不同.
句法
复杂
订单(!)
模拟所有这些以生成合理的虚拟数据是非常困难的,除非您实际在mock中构建一个小数据库,它解释传输的SQL语句.话虽如此,使用一个众所周知的集成测试数据库,您可以使用众所周知的数据轻松重置,您可以使用它来运行集成测试.
我使用第一个(针对测试数据库运行代码)。我看到您通过这种方法提出的唯一实质性问题是模式不同步的可能性,我可以通过在数据库中保留版本号并通过脚本对所有模式进行更改来处理此问题,该脚本对每个版本增量应用更改。
我还首先对测试环境进行了所有更改(包括对数据库架构的更改),因此最终导致了另一种方式:在所有测试通过之后,将架构更新应用于生产主机。我还在开发系统上保留了一对单独的测试数据库和应用程序数据库,以便在接触实际生产环境之前可以验证数据库升级是否正常。