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