Skip to main content

Unit testing and PInvoking

I'm big on unit testing, I like to test everything and I feel uncomfortable when I'm unable to write tests for some of my code. So even though some parts of your application are hard to test I'm willing to take some extra time to make them testable. In this post I want to show you some tricks I use to unit-test code that uses platform invoked functions.

As an example I'll use one of the invokes from my previous post, SetupDiClassNameFromGuid from the setupapi.dll. If you want to know more about what this does can read that post. Now let's take a look at some code.

   1: public class PInvoking
   2: {
   3:     [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
   4:     public static extern bool SetupDiClassNameFromGuid( 
   5:         ref Guid classGuid, StringBuilder className, UInt32 classNameSize, ref UInt32 requiredSize);
   6:  
   7:     public void DoSomethingThatNeedsToBeTested() 
   8:     {
   9:         var className = new StringBuilder();
  10:         var classGuid = Guid.Empty;
  11:         var reqSize = (uint)0;
  12:  
  13:         // Some code that needs to be tested
  14:         // ..
  15:         
  16:         // PInvode preprocessing
  17:         // ..
  18:  
  19:         SetupDiClassNameFromGuid(ref classGuid, className, reqSize, ref reqSize);
  20:  
  21:         // PInvoke postprocessing
  22:         // ..
  23:  
  24:         // Some more code
  25:         // ..
  26:     }
  27: }

I can see two problems here, testability and reusability. Let's start with making this more reusable although this has nothing to do with unit testing yet.


If you read the previous post you'll know that this call needs some pre-, and post-processing to get something usefull out of it. These technical details are not something you want to bother the consuming function with. DoSomethingThatNeedsToBeTested() just wants to pass a Guid in and get a string back. And in the real world there are probably more consumers of this imported function so you'll want to get rid of this duplicated code and hide it somewhere you can reuse it with a single call.


You can split out the preprocessing and postprocessing into a separate function but it would be better to go a bit further and just put everything in a separate class. I even made the DllImport private so the consumers are forced to use the code with all the wrapping code. I used the wrapper code from my previous post here to show how fast things get complex. In real life you'll probably want to add some errorhandling too.



   1: public class SetupApi
   2: {
   3:     [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
   4:     private static extern bool _SetupDiClassNameFromGuid(
   5:         ref Guid classGuid, StringBuilder className, UInt32 classNameSize, ref UInt32 requiredSize);
   6:  
   7:     public static bool SetupDiClassNameFromGuid(Guid classGuid, ref string className)
   8:     {
   9:         // 50 is a Sensible default value, if we're lucky it's enough
  10:         uint reqSize = 50;
  11:         bool returnValue;
  12:         StringBuilder classNameBuilder = new StringBuilder((int)reqSize);
  13:  
  14:         // First try.
  15:         returnValue = _SetupDiClassNameFromGuid(
  16:             ref classGuid, classNameBuilder, (uint)classNameBuilder.Capacity, ref reqSize);
  17:  
  18:         if ((uint)classNameBuilder.Capacity != reqSize)
  19:         {
  20:             // call again with the right size stringbuilder
  21:             classNameBuilder.Capacity = (int)reqSize;
  22:             returnValue = _SetupDiClassNameFromGuid(
  23:                 ref classGuid, classNameBuilder, reqSize, ref reqSize);
  24:         }
  25:  
  26:         className = classNameBuilder.ToString();
  27:         return returnValue;
  28:     }
  29: }

Things are a bit more reusable now. But they're still not testable. And we've created a second problem. We don't just want to test the consuming code but we want to test the wrapper around _SetupDiClassNameFromGuid too.


The first problem is easy to solve. Just remove the static keyword from line 7. Then you can use dependency injection to inject this class into the consuming classes or functions and you can mock up everything. I'll show an example in my next post.


The second problem is a bit harder. I have a couple of solutions for this too but the are not very straight-forward and involve some extra code. I'll explain these later too.

Comments

Popular posts from this blog

Using xUnit.Net with .Net 4.0

I’ve been using xUnit.Net for a while now. It’s just a tiny bit cleaner and slightly less abrasive than other .Net unit testing frameworks. Leaving out unnecessary stuff like [TestFixture] and shortening Assert.AreEqual to the equally clear but shorter Assert.Equal don’t seem like big improvements but when you type them several times a day tiny improvements start to add up. I also like the use of the [Fact] attribute instead of [Test]. It shifts the focus from testing to defining behavior. So how do we get all this goodness working with the Visual Studio 2010 beta?

Running a Git repository on Ubuntu using Gitosis

20I’ve been using Git for a couple of small projects that I’ve been hosting on github.com but version control for my bigger ‘secret’ projects still runs on a windows machine with visual svn server.Now that I’m starting to use Mono for a couple of projects so I’m playing with linux more. Last week I decided to try to try out gitosis on an ubuntu server. I found out it’s pretty easy to use when you know your way around git but for a noob like me some things weren’t immediately clear. Eventually I solved most problems I ran into, so I decided to write up the steps I took to install gitosis on ubuntu 10.04

Android development resource links

I've been playing with the Android SDK and I have a growing list of bookmarks to Android dev resources for my own use. I thought the best place to keep them would be here on my blog. That way other people can benefit too. I'll keep updating this list so feel free to add suggestions in the comments.