我使用过很多web应用程序,它们都是由后台复杂程度各异的数据库驱动的。通常,有一个ORM层独立于业务和表示逻辑。这使得单元测试业务逻辑相当简单;事情可以在离散的模块中实现,测试所需的任何数据都可以通过对象模拟来伪造。

但是测试ORM和数据库本身总是充满了问题和妥协。

这些年来,我尝试了一些策略,但没有一个能让我完全满意。

Load a test database with known data. Run tests against the ORM and confirm that the right data comes back. The disadvantage here is that your test DB has to keep up with any schema changes in the application database, and might get out of sync. It also relies on artificial data, and may not expose bugs that occur due to stupid user input. Finally, if the test database is small, it won't reveal inefficiencies like a missing index. (OK, that last one isn't really what unit testing should be used for, but it doesn't hurt.) Load a copy of the production database and test against that. The problem here is that you may have no idea what's in the production DB at any given time; your tests may need to be rewritten if data changes over time.

有些人指出,这两种策略都依赖于特定的数据,单元测试应该只测试功能。为此,我看到了一些建议:

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

您在测试数据库驱动的应用程序时使用了哪些策略?对你来说最有效的方法是什么?


当前回答

出于以下原因,我总是对内存中的DB (HSQLDB或Derby)运行测试:

它使您考虑在测试DB中保留哪些数据以及为什么要保留这些数据。仅仅将您的生产DB拖到测试系统中就意味着“我不知道我在做什么,也不知道为什么,如果有什么东西坏了,那肯定不是我!!”;) 它确保可以在新的地方轻松地重新创建数据库(例如,当我们需要从生产中复制一个错误时)。 它极大地提高了DDL文件的质量。

一旦测试开始,内存中的DB就会加载新的数据,在大多数测试之后,我调用ROLLBACK以保持它的稳定。始终保持测试DB中的数据稳定!如果数据一直在变化,就不能进行测试。

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

其他回答

对于基于JDBC的项目(直接或间接地,例如JPA, EJB,…),您不能模拟整个数据库(在这种情况下,在真正的RDBMS上使用测试数据库会更好),但只能在JDBC级别模拟。

这样做的好处是抽象,因为JDBC数据(结果集、更新计数、警告……)无论在后端是什么:您的prod db、测试db,还是为每个测试用例提供的一些模型数据,都是相同的。

通过为每种情况模拟JDBC连接,不需要管理测试db(清理,一次只进行一个测试,重新加载fixture,等等)。每个模拟连接都是隔离的,不需要清理。每个测试用例中只提供了模拟JDBC交换所需的最低限度的fixture,这有助于避免管理整个测试数据库的复杂性。

Acolyte是我的框架,它包括用于这种模型的JDBC驱动程序和实用程序:http://acolyte.eu.org。

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

Keep the entire schema and scripts for creating it in source control so that anyone can create the current database schema after a check out. In addition, keep sample data in data files that get loaded by part of the build process. As you discover data that causes errors, add it to your sample data to check that errors don't re-emerge. Use a continuous integration server to build the database schema, load the sample data, and run tests. This is how we keep our test database in sync (rebuilding it at every test run). Though this requires that the CI server have access and ownership of its own dedicated database instance, I say that having our db schema built 3 times a day has dramatically helped find errors that probably would not have been found till just before delivery (if not later). I can't say that I rebuild the schema before every commit. Does anybody? With this approach you won't have to (well maybe we should, but its not a big deal if someone forgets). For my group, user input is done at the application level (not db) so this is tested via standard unit tests.

正在加载生产数据库副本: 这是我在上一份工作中使用的方法。这是一个巨大的痛苦,因为有几个问题:

副本会比生产版本过时 将对副本的模式进行更改,但不会传播到生产系统。在这一点上,我们有不同的模式。不好玩。

模拟数据库服务器: 我现在的工作也是这样。在每次提交之后,我们对注入了模拟db访问器的应用程序代码执行单元测试。然后我们每天执行三次上面描述的完整的db构建。我绝对推荐这两种方法。

我一直在问这个问题,但我认为没有解决这个问题的灵丹妙药。

我目前所做的是模拟DAO对象,并在内存中保持一个良好的对象集合的表示,这些对象表示可能存在于数据库中的有趣的数据情况。

The main problem I see with that approach is that you're covering only the code that interacts with your DAO layer, but never testing the DAO itself, and in my experience I see that a lot of errors happen on that layer as well. I also keep a few unit tests that run against the database (for the sake of using TDD or quick testing locally), but those tests are never run on my continuous integration server, since we don't keep a database for that purpose and I think tests that run on CI server should be self-contained.

我发现另一种方法非常有趣,但并不总是值得的,因为它有点耗时,那就是在只在单元测试中运行的嵌入式数据库上创建用于生产的相同模式。

尽管毫无疑问,这种方法提高了您的覆盖率,但也有一些缺点,因为您必须尽可能接近ANSI SQL,以使其与当前的DBMS和嵌入式替代品一起工作。

无论您认为哪个项目与您的代码更相关,都有一些项目可以使它更简单,比如DbUnit。

出于以下原因,我总是对内存中的DB (HSQLDB或Derby)运行测试:

它使您考虑在测试DB中保留哪些数据以及为什么要保留这些数据。仅仅将您的生产DB拖到测试系统中就意味着“我不知道我在做什么,也不知道为什么,如果有什么东西坏了,那肯定不是我!!”;) 它确保可以在新的地方轻松地重新创建数据库(例如,当我们需要从生产中复制一个错误时)。 它极大地提高了DDL文件的质量。

一旦测试开始,内存中的DB就会加载新的数据,在大多数测试之后,我调用ROLLBACK以保持它的稳定。始终保持测试DB中的数据稳定!如果数据一直在变化,就不能进行测试。

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

即使有允许您以某种方式模拟数据库的工具(例如jOOQ的MockConnection,可以在这个回答中看到-免责声明,我为jOOQ的供应商工作),我也建议不要用复杂的查询模拟大型数据库。

即使您只是想对ORM进行集成测试,也要注意ORM会向数据库发出一系列非常复杂的查询,这些查询可能在

语法 复杂性 订单(!)

模拟所有这些以产生合理的虚拟数据是相当困难的,除非您实际上在模拟中构建了一个小数据库,它解释传输的SQL语句。话虽如此,请使用一个知名的集成测试数据库,您可以很容易地使用知名数据重置该数据库,并针对该数据库运行集成测试。