Writing a .NET unit testing framework in 100 lines of code
Some time ago, I got frustrated with setting up NUnit for simple testing projects. I didn’t want to bother with the test runner, and I didn’t want to mess with any tooling in Visual Studio. My lazy default for doing the tests was to create a new console application and use it to drive the system under test.
After a while I figured it was about time to at least put my assertion code into a project and reuse that every time I did a lazy console test runner. You know how these things go after that. I figured it couldn’t be too hard to support using .NET attributes to mark test cases and maybe do test fixtures. I ended up with a complete unit-testing framework including assertions in under 100 lines of code. You can see the result on github.
In order to enable something like this:
[FestTest]
public void FirstTest() {
// do AAA here
}
We need to get all methods in the assembly that are adorned with the FestTest attribute and run them. Seems simple enough, here is the basic framework:
Assembly executingAssembly = Assembly.GetCallingAssembly();
Type[] types = executingAssembly.GetTypes();
foreach( Type type in types ) {
MethodInfo[] methods = type.GetMethods();
foreach( MethodInfo method in methods ) {
object[] attributes = method.GetCustomAttributes( typeof( FestTest ), true );
if( attributes.Length > 0 ) {
// Invoke the test method here
}
}
}
In my final version of the code I decided to support the use of test fixtures using attributes as well. That is, you are able to inject a new instance of a fixture into a test method using something like this:
// test fixture we'd like to inject into the test
public class MyFixture {
public string teststring = "teststring";
}
[FestTest]
[FestFixture( typeof( MyFixture ) )]
public void Test1( MyFixture myfixture ) {
...
Fest.AssertEqual<string>( myfixture.teststring, "teststring");
}
This added to the complexity of the code significantly, but still the library comes in at under 100 lines. I’ll post some details about how I did this in a future blog post.