Warning: This post is mostly about Unit testing and less about Sharpduino as a library.
It has been almost two months now that I have been developing the next version of the sharpduino library. I feel that the new implementation is far better in terms of understanding what each part of the code does, with as few byte constants as possible, so anyone who looks at the code can understand what is happening without having the firmata.org page or (shameless plug) my blog open. I tried to make various parts of the library as loosely coupled as possible, so it could be extensible and easier to code against.
In the previous version the library was tested by a console application trying to make sure that everything worked for the end users by telling them what to do (connect LEDS, etc.) and then what to expect as the library was used (Led turns on/off, etc).
For this version much of the functionality was pre-tested with unit tests before even hooking up an arduino. This was done with the help of the NUnit and Moq libraries. I won’t be going into detail about the choice of Unit testing and Mocking frameworks. Let’s just say that the NUnit – Moq combination worked for me without much hustle. If you are interested in finding alternative frameworks, the following is a small list which I made while searching for my library. This is not an exhaustive list, but a few of the most prevalent frameworks that I could find.
Unit Testing Frameworks: NUnit, XUnit.net,MSTest, MbUnit
Moq Frameworks: NMock,RhinoMocks,Moq,NSubstitute
Until recently I had no idea what this Mocking stuff was really about. I had written unit tests but almost always I used business objects, or stubs. -There is a nice post about stubs and mocks from Martin Fowler http://martinfowler.com/articles/mocksArentStubs.html –. So what is this thing with Mocking? Let’s see an example from the Sharpduino library.
This was the EasyFirmata class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class EasyFirmata : FirmataBase, IHandleAllMessages { ComPort port; public EasyFirmata(string comPortName) { // Code to initialize com port ... } ... protected override Dispose(bool shouldDispose) { // Dispose code port.Dispose(); ... } } |
I have omitted the irrelevant code. As you can see there is a close relationship with the com port. In order to check if the port is disposed when the EasyFirmata is disposed we cannot write any test other than believe that it will get called. There is also the problem of being dependent on a serial port being available for all the other tests. The solution to this problem is to abstract the base com port usage into an interface. Thus the ISerialProvider interface
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/// ; /// This is an abstraction of a provider of serial data, /// for example a com port or some networking port /// ; public interface ISerialProvider : IDisposable { /// ; /// Opens the current serial provider /// ; void Open(); /// ; /// Closes the current serial provider /// ; void Close(); /// ; /// This is the providers event for incoming data /// ; event EventHandler; DataReceived; /// ; /// Use the provider to send some bytes constituting a message /// ; void Send(IEnumerable; bytes); } |
Creating a ComPortProvider which implements ISerialProvider is very straightforward. The EasyFirmata class now becomes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class EasyFirmata : FirmataBase, IHandleAllMessages { ISerialProvider serialProvider; public EasyFirmata(ISerialProvider serialProvider) { // Just save the provider object to a field this.serialProvider = serialProvider; ... } ... protected override Dispose(bool shouldDispose) { // Dispose code serialProvider.Dispose(); ... } } |
Now it becomes much easier to test the proper disposal of the port. We only have to create a new class which implements ISerialProvider. For this test we just need to implement the Dispose method, so that it tracks if it has been called or not. The test then is easy to setup and execute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
internal class StubProvider : ISerialProvider { public int TimesDisposed = 0; public void Dispose(){TimesDisposed++;} public void Open(){} public void Close(){} public event EventHandler; DataReceived; public void Send(IEnumerable; bytes){} } ... [Test] public void Should_Call_Dispose_On_The_Serial_Provider() { var prov = new StubProvider(); using (var moqFirmataEmptyBase = new FirmataEmptyBaseStub(prov)){} Assert.AreEqual(prov.TimesDisposed,1); } |
This was easy. But as we need to test more and more functionality, we have to write a lot more code for our Stub class. Mocks are used to spare us from this implementation and give an alternate way of doing such tests.
Instead of creating and keeping track of some state we just describe their behavior (how they respond to various stimuli -method calls,property changes,etc-) and then we verify how they reacted to the actual calls.
For example take this test:
1 2 3 4 5 6 7 |
[Test] public void EasyFirmata_Is_Disposed_Without_Errors() { var mockSerialProvider = new Mock;(); using (var firmata = new EasyFirmata(mockSerialProvider.Object)){} mockSerialProvider.Verify(x =>; x.Dispose(),Times.Once()); } |
Here we create a Mock object which implements our interface. Since we only need to check that a method was called we don’t have to setup any behaviors. We feed our object to the EasyFirmata constructor and wait for the disposal of the EasyFirmata object. We then proceed to verify if the Dispose method was called in our Mock object.
As you can see, even for our simplest example we wrote substantially less code by using a Mock framework. For more complex scenarios, this difference really makes the developer’s life easier. However, mock objects are not a panacea for all testing needs. There are scenarios where it will be much more difficult to describe a mock behavior than just creating a stub. It is the job of the developer to choose the best approach for each scenario.
BTW. Live Writer sucks. I wrote most of this post at least twice because of random crashes. And although there is an auto-recover functionality, it doesn’t always work (as I learnt the hard way). That said I will probably use it again for my next post..