Posts Tagged ‘Testing’
Improving Integration Tests In .Net By Using Attributes To Execute SQL Scripts
A common problem I find while writing integration tests is the lack of a nice way to control the database. Since it is an integration test, you don’t want to mock out the database completely, just set it up with the required test data. As part of the Arrange step of the Arrange, Act, Assert loop, it is common to want to run some SQL scripts to set up your database and another script to clean up after the test finishes. This leads to various solutions such as having a standard method invocations at the start and end of the test to run the appropriate SQL scripts. These methods could even be inside of a test base class.
public void UpdateUserNameTest() { ExecuteSqlScript(@"SetupTestUserDetails.sql"); // Act. Call the appropriate methods and Assert everything went well. ExecuteSqlScript(@"CleanupTestUserDetails.sql"); }
One of the problems with that is that if the “Act” part of your test throws an exception, the clean up script might never get run. This will require you to add a try catch block around your test to catch exceptions and always run the clean up scripts. This makes the code a bit more messy by increasing the plumbing noise around your main logic. Wrapping the test in a SQL transaction has it’s own problems. The changes made by the set up script won’t be visible outside the transaction. This can be a problem if you are making service calls as you do in integration tests.
A neater of doing the same thing in my opinion is taking a more Aspect Oriented approach. Since the set up of the test data and the clean up are not logic being tested, they can be yanked up to become Attributes. But the built in [TestInitialize] and [TestCleanup] attributes in MS test framework don’t take any parameters which makes it hard to run different SQL scripts for each test. This requires the use of an AOP framework that allows attribute pointcuts. The problem with using Attributes and AOP is that the built in MS Test Runner that comes with Visual Studio cannot be made to get the test classes from an AOP container rather than just new’ing them up. This rules out the usage of runtime weavers such as Spring. Compile time weavers such as Postsharp would make life so much easier. But often, when you start on a project, the AOP framework that is used by the team is already set.
To get around this problem I found a clean solution using a feature that is already built in .Net called “Context Bound Objects”. Objects derived from ContextBoundObject allow you to inject custom services into the object’s interception chain. More information about Context Bound Objects can be found here . As part of creating a new context for a Context Bound Object, we have the chance to execute our own code. A diagram of this architecture is shown below.
We can use this process to create our “Aspectable Test” as shown by Callum Hibbert here along with his Codeplex project.
Since taking a dependency on a third party dll can be a contentious issue requiring many team meetings, I have simplified and isolated the releavant parts to only a few classes that can be added into any project. Callum’s framework is a very extensible and highly decoupled. I have distilled this to 5 tightly coupled classes that will get the job done. This is available for download at the bottom of this post.
The key classes are the AspectOrientedUnitTest, which inherits from ContextBoundObject. It is also decorated with a [AspectOrientedUnitTestContext] attribute which is an attribute that derives from ContextAttribute. Here, we override the “GetPropertiesForNewContext” method to inject in our own aspect. In our case, the aspect is the SqlExecuteAspect.
I have removed all the indirection from the TestExtension Framework to make this class implement the required interfaces of IMessageSink, IContextProperty, IContributeObjectSink. The method of interest is the “SyncProcessMessage” method in this class. Here, we can execute code before and after we pass the call to the next object in the message sink chain. We wrap up the actual method execution in a try/finally block to make sure the clean up sql always runs. This is shown below.
public IMessage SyncProcessMessage(IMessage msg) { if (msg == null) throw new ArgumentNullException("msg"); // Pre processing code ProcessAttributes(msg); IMessage returnMessage; try { // The following line is the call to the actual method we have intercepted. We can do stuff // before we call this or after. This lets us change behaviour. returnMessage = _nextSink.SyncProcessMessage(msg); } finally { // Post processing code ProcessAnyPostSqlAttributes(msg); } return returnMessage; }
Here, we can examine the MethodInfo object in the IMessage object to see if its been decorated with any of the attributes we are interested in. If it is, we can extract the parameters from the attributes and execute the appropriate code. Using this pattern, now we can have a very nice attribute driven unit test, that lets us set up and tear down data in our database for our tests as shown below.
[PreSqlExecute("SetupTestUserDetails.sql")] [PostSqlExecute("CleanupTestUserDetails.sql")] public void UpdateUserNameTest() { // Act. Call the appropriate methods and Assert everything went well. }
Note that this also requires the unit test class itself to derive from our ContextBoundObject derived class like so …
public class UnitTest1 : AspectOrientedUnitTest { ... }
The performance hit associated with having to derive from the ContextBoundObject and it’s impact on your object model preclude it from being used as part of your application classes. But for Unit or Integration testing, it shouldn’t really matter much. The most important thing is that it we achieve this with very little code and without taking any dependency on other libraries or frameworks or having to use a completely different testing tool or framework. It should be very easy to introduce this into any .Net code base on any project in less than half a day without having to seek a team wide consensus or approval. Try it out and let me know what you think.
