Testable Voice Activated Toaster
July 27, 2010 Leave a comment
In following up to last week’s voice activated toaster, I wanted to work on making my solution more testable. To that end, I re-read The Art Of Unit Testing:
I still find his explanation of Mocks versus Stubs confusing. In any event, I dove right in and added a test project to my solution. I first realized that Rhino Mocks works either with Interfaces or Abstract Classes – the Phidget API had neither. I read Brownfield Application Development In .NET:
and realized that I needed to add an anti-corruption layer to the Phidget API and use an Adapter Pattern to wrap the existing API.
Moving to my code, the 1st stumbling block is that the SpeechRecognitionEngine and Choices classes do not have a defined interface. I refactored the CreateSpeechRecognitionEngine method to a more testable chunks of code:
public static void SetupSpeechRecognition()
{
SpeechRecognitionEngine _speechRecognitionEngine = new SpeechRecognitionEngine();
Choices _choices = new Choices();
CreateSpeechRecognitionEngine(_speechRecognitionEngine);
LoadGrammarIntoSpeechRecognitionEngine(_speechRecognitionEngine, _choices);
_speechRecognitionEngine.RecognizeAsync(RecognizeMode.Multiple);
}
public static void CreateSpeechRecognitionEngine(SpeechRecognitionEngine speechRecognitionEngine)
{
SpeechRecognitionEngine _speechRecognitionEngine = speechRecognitionEngine;
_speechRecognitionEngine.SetInputToDefaultAudioDevice();
_speechRecognitionEngine.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(speechRecognitionEngine_SpeechRecognized);
}
public static void LoadGrammarIntoSpeechRecognitionEngine(SpeechRecognitionEngine speechRecognitionEngine, Choices choices)
{
SpeechRecognitionEngine _speechRecognitionEngine = speechRecognitionEngine;
Choices _choices = choices;
_choices.Add("Toast");
_choices.Add("Stop");
Grammar grammar = new Grammar(_choices);
_speechRecognitionEngine.LoadGrammar(grammar);
}
I then used the adapter pattern to make a testable Phidget API. I created interfaces for a Phidget, Interface Kit, and a PhidgetException. I then created Phidget, Interface, and PhidgetException classes that implemented the interface. I also split out the all of the enums that the API uses into their own class – the values are the same thanks to Reflector. My solution looked like this:
I then had some supporting classes to take care of the rest of the API. The first was the event handlers. For the sake of keeping the class count down, I put all of the event handers into one class:
namespace Com.Tff.VoiceActivatedToaster.Phidget.Events
{
//Phidgit Events
public delegate void AttachEventHandler(object sender, AttachEventArgs e);
public delegate void DetachEventHandler(object sender, DetachEventArgs e);
public delegate void ErrorEventHandler(object sender, ErrorEventArgs e);
public delegate void ServerConnectEventHandler(object sender, ServerConnectEventArgs e);
public delegate void ServerDisconnectEventHandler(object sender, ServerDisconnectEventArgs e);
//Interface Kit Events
public delegate void OutputChangeEventHandler(object sender, OutputChangeEventArgs e);
public delegate void SensorChangeEventHandler(object sender, SensorChangeEventArgs e);
}
I then added individual classes for each event args. The Event Subdirectory looked like so:
Hooking up the events to the Phidget API, I made some good progress. I then had 3 public fields (sigh) on the Interface Kit: Inputs, Outputs, and Sensors. My 1st thought was to create interfaces and wrapper classes like I have done so far – but I immediately got a frying pan to the face:
Crap – the constructors are marked internal. I could not wrap the classes. I then went to plan B – use Reflector and implement the same code they used. I then got a frying pan to the back of the head with Plan B when I looked at their code:
They were wrapping their unmanged code so they have these pointers and references all over the place. I took a deep breath (“OK, I can do this”) and tried to implement. I tried to reference the unmanaged assembly and got this:
Plan B is officially dead. I then went to Plan C – use the Phidget API in my API. I hated to do it, but I did it. Here is the relevant code snippet:
public class InterfaceKit : Phidget, IInterfaceKit
{
Phidgets.InterfaceKit _interfaceKit = null;
//Have to use the Pdigit API b/c the Constructors are marked as internal
public Phidgets.InterfaceKitDigitalInputCollection inputs;
public Phidgets.InterfaceKitDigitalOutputCollection outputs;
public Phidgets.InterfaceKitAnalogSensorCollection sensors;
public InterfaceKit()
{
_interfaceKit = new Phidgets.InterfaceKit();
}
I then hooked up my console application to the API and got nothing:
I then ran out of time: 5 hours of coding for Unit Test – and I have yet to write a single test. I am just refactoring a poorly-written API to make the classes testable.
I’ll keep plugging away.