如何使用 NUnit 测试数据库相关代码?
我想用 NUnit 编写访问数据库的单元测试。 我希望每次测试时数据库都处于一致的状态。 我认为事务可以让我“撤消”每个测试,所以我四处搜索并找到了 2004-05 年关于该主题的几篇文章:
- http://weblogs.asp.net/rosherove/archive/2004/07/12/180189.aspx
- http://weblogs.asp.net/rosherove/archive/2004/10/05/238201.aspx
- http://davidhayden.com/blog/dave/archive /2004/07/12/365.aspx
- http ://haacked.com/archive/2005/12/28/11377.aspx
这些似乎解决了为 NUnit 实现自定义属性的问题,该属性内置了在每次测试执行后回滚数据库操作的能力。
太棒了,但是……
- 这个功能本身就存在于 NUnit 中吗?
- 这项技术在过去 4 年中是否得到了改进?
- 这仍然是测试数据库相关代码的最佳方法吗?
编辑:这并不是我想专门测试我的 DAL,而是我想测试与数据库交互的代码片段。 为了使这些测试成为“非接触式”且可重复的,如果我可以在每次测试后重置数据库,那就太棒了。
此外,我想将其简化到目前没有测试场所的现有项目中。 因此,我实际上无法为每个测试从头开始编写数据库和数据脚本。
I want to write unit tests with NUnit that hit the database. I'd like to have the database in a consistent state for each test. I thought transactions would allow me to "undo" each test so I searched around and found several articles from 2004-05 on the topic:
- http://weblogs.asp.net/rosherove/archive/2004/07/12/180189.aspx
- http://weblogs.asp.net/rosherove/archive/2004/10/05/238201.aspx
- http://davidhayden.com/blog/dave/archive/2004/07/12/365.aspx
- http://haacked.com/archive/2005/12/28/11377.aspx
These seem to resolve around implementing a custom attribute for NUnit which builds in the ability to rollback DB operations after each test executes.
That's great but...
- Does this functionality exists somewhere in NUnit natively?
- Has this technique been improved upon in the last 4 years?
- Is this still the best way to test database-related code?
Edit: it's not that I want to test my DAL specifically, it's more that I want to test pieces of my code that interact with the database. For these tests to be "no-touch" and repeatable, it'd be awesome if I could reset the database after each one.
Further, I want to ease this into an existing project that has no testing place at the moment. For that reason, I can't practically script up a database and data from scratch for each test.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
NUnit 现在有一个 [Rollback] 属性,但我更喜欢采用不同的方式。 我使用 TransactionScope 类。 有几种使用方法。
由于您没有告诉 TransactionScope 提交,它会自动回滚。 即使断言失败或抛出其他异常,它也能正常工作。
另一种方法是使用[SetUp]创建TransactionScope并使用[TearDown]对其调用Dispose。 它消除了一些重复的代码,但完成了同样的事情。
这与单个测试中的 using 语句一样安全,因为 NUnit 将保证调用 TearDown。
话虽如此,我确实认为访问数据库的测试并不是真正的单元测试。 我仍然编写它们,但我将它们视为集成测试。 我仍然认为它们提供了价值。 我经常使用它们的一个地方是测试 LINQ to SQL 代码。 我不使用设计师。 我手写 DTO 和属性。 众所周知我会弄错。 集成测试有助于发现我的错误。
NUnit now has a [Rollback] attribute, but I prefer to do it a different way. I use the TransactionScope class. There are a couple of ways to use it.
Since you didn't tell the TransactionScope to commit it will rollback automatically. It works even if an assertion fails or some other exception is thrown.
The other way is to use the [SetUp] to create the TransactionScope and [TearDown] to call Dispose on it. It cuts out some code duplication, but accomplishes the same thing.
This is as safe as the using statement in an individual test because NUnit will guarantee that TearDown is called.
Having said all that I do think that tests that hit the database are not really unit tests. I still write them, but I think of them as integration tests. I still see them as providing value. One place I use them often is in testing LINQ to SQL code. I don't use the designer. I hand write the DTO's and attributes. I've been known to get it wrong. The integration tests help catch my mistake.
我刚刚去了一个 .NET 用户组,演示者说他在测试设置和拆卸中使用了 SQLlite,并使用了内存选项。 他必须稍微伪造连接并显式地破坏连接,但每次都会给出一个干净的数据库。
http://houseofbilz.com/存档/2008/11/14/update-for-the-activerecord-quotmockquot-framework.aspx
I just went to a .NET user group and the presenter said he used SQLlite in test setup and teardown and used the in memory option. He had to fudge the connection a little and explicit destroy the connection, but it would give a clean DB every time.
http://houseofbilz.com/archive/2008/11/14/update-for-the-activerecord-quotmockquot-framework.aspx
我将这些称为集成测试,但没关系。 我对此类测试所做的就是让测试类中的设置方法在每次测试之前清除所有感兴趣的表。 我通常手动编写 SQL 来执行此操作,这样我就不会使用被测试的类。
一般来说,我的数据层依赖于 ORM,因此我不会在那里编写太多单元测试。 我觉得没有必要对我不编写的代码进行单元测试。 对于我在层中添加的代码,我通常使用依赖项注入来抽象出与数据库的实际连接,以便当我测试代码时,它不会接触实际的数据库。 与模拟框架结合执行此操作以获得最佳结果。
I would call these integration tests, but no matter. What I have done for such tests is have my setup methods in the test class clear all the tables of interest before each test. I generally hand write the SQL to do this so that I'm not using the classes under test.
Generally, I rely on an ORM for my datalayer and thus I don't write unit tests for much there. I don't feel a need to unit test code that I don't write. For code that I add in the layer, I generally use dependency injection to abstract out the actual connection to the database so that when I test my code, it doesn't touch the actual database. Do this in conjunction with a mocking framework for best results.
对于此类测试,我尝试使用 NDbUnit(与 NUnit 协同工作)。 如果没记错的话,它是 Java 平台上的 DbUnit 的移植版。 它有很多灵活的命令来满足您想要做的事情。 该项目似乎已移至此处:
http://code.google.com/p/ndbunit/< /a>
(它曾经位于 http://ndbunit.org)。
来源似乎可以通过以下链接获得:
http://ndbunit.googlecode.com/svn/trunk/
For this sort of testing, I experimented with NDbUnit (working in concert with NUnit). If memory serves, it was a port of DbUnit from the Java platform. It had a lot of slick commands for just the sort of thing you're trying to do. The project appears to have moved here:
http://code.google.com/p/ndbunit/
(it used to be at http://ndbunit.org).
The source appears to be available via this link:
http://ndbunit.googlecode.com/svn/trunk/
考虑创建一个数据库脚本,以便您可以从 NUnit 自动运行它,也可以手动运行它以进行其他类型的测试。 例如,如果使用 Oracle,则从 NUnit 中启动 SqlPlus 并运行脚本。 这些脚本通常编写起来更快并且更容易阅读。 此外,非常重要的是,从 Toad 或等效工具运行 SQL 比从代码运行 SQL 或从代码执行 ORM 更具启发性。 一般来说,我会创建一个安装和拆卸脚本,并将它们放入安装和拆卸方法中。
是否应该从单元测试中检查数据库是另一个讨论。 我相信这样做通常是有意义的。 对于许多应用程序来说,数据库是绝对的操作中心,逻辑是高度基于集合的,所有其他技术、语言和技术都是过眼云烟。 随着函数式语言的兴起,我们开始意识到 SQL 和 JavaScript 一样,实际上是一种伟大的语言,这些年来一直就在我们眼皮子底下。
顺便说一句,Linq to SQL(我在概念上喜欢它,尽管从未使用过)在我看来几乎像是一种在代码中执行原始 SQL 的方法,而无需承认我们正在做的事情。 有些人喜欢 SQL 并且知道他们喜欢它,其他人喜欢它但不知道他们喜欢它。 :)
Consider creating a database script so that you can run it automatically from NUnit as well as manually for other types of testing. For example, if using Oracle then kick off SqlPlus from within NUnit and run the scripts. These scripts are usually faster to write and easier to read. Also, very importantly, running SQL from Toad or equivalent is more illuminating than running SQL from code or going through an ORM from code. Generally I'll create both a setup and teardown script and put them in setup and teardown methods.
Whether you should be going through the DB at all from unit tests is another discussion. I believe it often does make sense to do so. For many apps the database is the absolute center of action, the logic is highly set based, and all the other technologies and languages and techniques are passing ghosts. And with the rise of functional languages we are starting to realize that SQL, like JavaScript, is actually a great language that was right there under our noses all these years.
Just as an aside, Linq to SQL (which I like in concept though have never used) almost seems to me like a way to do raw SQL from within code without admitting what we are doing. Some people like SQL and know they like it, others like it and don't know they like it. :)
对于像我一样最近来到这个帖子的人,我想推荐尝试 Reseed我目前正在为这个特定案例开发库。
内存数据库替换(缺乏功能)和事务回滚(事务不能嵌套)都不适合我,因此我最终采用了一个简单的删除/插入循环来实现数据恢复目的。 最终得到了一个库来生成这些内容,同时尝试优化我的测试速度和设置的简单性。 如果它能帮助其他人,我会很高兴。
我建议的另一种选择是使用数据库快照来恢复数据,它具有相当的性能和可用性。
工作流程如下:
如果您可以为所有测试拥有唯一的数据脚本,并允许您唯一一次执行插入(应该是最慢的),那么它是合适的,而且您根本不需要数据清理脚本。
为了进一步提高性能,因为此类测试可能需要大量时间,请考虑使用数据库池并测试并行化。
For anyone coming to this thread these days like me, I'd like to recommend trying the Reseed library I'm developing currently for this specific case.
Neither in-memory db replacement (lack of features) nor transaction rollback (transactions can't be nested) were a suitable option for me, so I ended up with a simple delete/insert cycle for the data restore purpose. Ended up with a library to generate those, while trying to optimize my tests speed and simplicity of setup. Would be happy if it helps anyone else.
Another alternative I'd recommend is using database snapshots to restore data, which is of comparable performance and usability.
Workflow is as follows:
It's suitable if you could have the only data script for all the tests and allows you to execute the insertion (which is supposed to be the slowest) the only time, moreover you don't need data cleanup script at all.
For further performance improvement, as such tests could take a lot of time, consider using a pool of databases and tests parallelization.