| Scott 的个人资料Software Development Wit...日志列表 | 帮助 |
|
3月22日 Database Unit Testing in .NET 2.0 with TransactionalFixtureI'm a big fan of unit testing. I also tend to do a lot of persistence layer work. So I often face the problem of restoring a test database to a known state before each test.
My favorite solution to this problem is to run each test inside a distributed transaction which is rolled back at the end, thereby returning the database to the initial state. In most development environments, this is considerably faster than the canonical solution of dropping and recreating the test database for each test.
I first discovered the excellent distributed-transaction idea in this blog post by Roy Osherove. He presents a special NUnit TestFixture called "DatabaseFixture" which codifies the approach using the test's SetUp and TearDown methods. Personally, I prefer the name "TransactionalFixture" suggested by one of the commenters, because after all, the technique is equally applicable to tests using other transactional resources (most notably message queues).
I've been wanting to port this technique to the new System.Transactions classes available in .NET 2.0. Tonight I finally got a chance to play with that and see how it comes out. My new TransactionalFixture class (almost embarrassingly simple) is as follows:
using System;
using System.Transactions; using NUnit.Framework; namespace TransactionalFixtureLib { [TestFixture] public abstract class TransactionalFixture { /// <summary> /// Transaction that we hold open for the duration of the test. /// </summary> private System.Transactions.TransactionScope testTransaction; [SetUp] public void SetUp() { testTransaction = new TransactionScope(); } [TearDown] public void TearDown() { testTransaction.Dispose(); } } } In a nutshell, if you make your NUnit tests extend TransactionalFixture, all of their operations on transactional resources will roll back at the end of the test. Within the test itself, of course, the changes are visible, so you can perform any setup and verification that your test requires.
With regard to the implementation of TransactionalFixture, I would ordinarily strongly recommend against holding any disposable resources (like TransactionScope, or DbConnection) as member variables in a class that is not itself disposable. However, in this case, we know that the TextFixture contract implemented by NUnit ensures that our resource will be cleaned up in the TearDown call.
Using System.Transactions has at least one substantial benefit over the .NET 1.x approach presented previously: Your test library no longer needs to be hosted in a serviced component. Among other things, that means it and everything it references no longer have to be strong-named.
I'll finish with a couple of usage notes that apply to both .NET 1.x and 2.0 versions of TransactionalFixture. First, I said that when the outer transaction rolls back in TearDown, the database is restored to the initial state. That's not entirely correct -- if you caused any auto-increment identity columns to come into play, they will continue to tick upward even as these transactions are rolled back. Of course, your application should never care about this, but you should be aware lest anyone be surprised by this behavior. Lastly, if you're debugging a transactional test, you'll want to make sure you're on an isolated database instance, because it's quite likely that many database resources will remain locked for the duration of a test. In a team environment, it's a best practice to have everyone develop against their own version of the database anyway, and in that case, this is a non-issue. 引用通告此日志的引用通告 URL 是: http://softwaredevscott.spaces.live.com/blog/cns!1A9E939F7373F3B7!155.trak 引用此项的网络日志
|
|
|