This ASP.NET web application is designed to give a very simple example of how tests may be used. The application itself is very elementary. It does three things:
- It offers a means for a user to translate a single digit into a French word. All French translations are facilitated exclusively in JavaScript.
- It offers a means for a user to translate a single digit into a German word. All German translations are facilitated exclusively in C#.
- It writes all German-translated single digit numbers to a text file.
There are 29 tests wrapped around the logic of the three items above. I'd encourage you to try to change them around and to see what happens when they fail upon your alterations. These 29 tests are found in five locations. They are:
- In the WebApplication project at \Scripts\widgets\FrenchTranslator\ there is an .html file titled "SpecRunner.html" which may be opened in or independent of Visual Studio to run the tests defined in "FrenchTranslator.spec.js" (which tests the code in "FrenchTranslator.js" using the HTML in "fixture.html"). There are thirteen tests here which test the French translations. A framework called Jasmine was used to write a JavaScript widget for the French translations as Jasmine's logic may be tested with Jasmine's tests. Unfortunately, this does mean doing things the Jasmine way and that can be a sizable departure from other JavaScript/jQuery implementations. I was recently told that Mocha.js is a better, less awkward tool for this sort of testing. Perhaps it is. The Jasmine way of doing things involves making a folder with four files in it with the latter three serving to test the first:
- a JavaScript widget
- an HTML fixture in the shape of the HTML the widget expects to manipulate and interact with
- a JavaScript specification for testing the widget
- an HTML test runner for running the specification for testing the widget
- The next four places where tests are found are all in files in the WebApplication.Tests project. The project itself is a test project and thus its tests may be run from within Visual Studio 2010 at: Tests > Run > All Tests in Solution …In particular, BasicUnitTests.cs tests the logic in GermanTranslator.cs back in the WebApplication project. GermanTranslator.cs in turn drives the German translations as you might guess from the name. There are thirteen tests here on the C# side which parallel those in the JavaScript side mentioned previously.
- FullSystemTest.cs should spawn a WatiN test. You should see Internet Explorer open and then see fields populated and buttons clicked as though the browser were being manipulated by a user. Selenium is another popular framework for this sort of thing and I've recently heard of another yet called phantomjs. I put three second delays in the one test here so that you may observe what it does in a browser in lieu of merely having a browser open, flicker quickly, and then close.
- In order to write tests you have to write code that is testable, and by that I mean code that is somewhat isolated without external dependencies bleeding into it. This can be a challenge. Code that has many tentacles out to many things cannot be tested without much pain and probably will have to be refactored, before any testing really begins, to isolate that which should be tested. For example, in this application, the calls to GermanTranslator.cs are done by GermanTranslatorWrapper.cs which also attempts the file writing. You would not want to test GermanTranslatorWrapper.cs as the app uses it because if you did so you would end up writing to the text file inappropriately as you tested, contaminating user-driven contents there with your own test-driven content. The File I/O stuff here is an example of an external dependency, and MockTest.cs expositions a way to test GermanTranslatorWrapper.cs while sidestepping the external dependency. Again, the nature of the code to be tested must be accommodating to testing, and to be accommodating I isolated the File I/O piece into FileWriting.cs in the Infrastructure folder of WebApplication and then handed it into GermanTranslatorWrapper.cs at a constructor. The constructor takes an IFileWriting interface at its signature and we are able to hand a FileWriting object in nonetheless as FileWriting inherits from IFileWriting allowing for an implicit upcasting. What do I mean by implicit upcasting? Imagine an Animal:
public class Animal
{
}
Then imagine a Parrot that inherited from Animal:
public class Parrot : Animal
{
}
You could explicitly cast a Parrot to its parent type like so:
Parrot parrot = new Parrot();
Animal animal = (Animal) parrot;
You could also implicitly cast a Parrot to an Animal by handing it into a constructor signature that expected an Animal such as this one:
public class AnimalHandler
{
public AnimalHandler(Animal animal)
{
Animal handledAnimal = animal;
}
}
In this case, providing a child type of the type specified in the constructor signature will not cause an error. Instead, C# allows for the child to be implicitly upcast to a parent. That makes this legitimate:
Parrot parrot = new Parrot();
AnimalHandler animalHandler = new AnimalHandler(parrot);
Moving forward, we may also make a class inherit from an interface instead of a parent class. (We have to use interfaces with a mocking framework. That is how the magic work.) What if we had an interface called IParrot?
public interface IParrot
{
}
What if Parrot inherited from IParrot instead of Animal?
public class Parrot : IParrot
{
}
Well, then we could have an explicit upcasting like so:
Parrot parrot = new Parrot();
IParrot iParrot = (IParrot) parrot;
We could also hand a Parrot into a constructor signature of this nature:
public class AnimalHandler
{
public AnimalHandler(IParrot iParrot)
{
IParrot handledAnimal = iParrot;
}
}
And, have implicit upcasting like so:
Parrot parrot = new Parrot();
AnimalHandler animalHandler = new AnimalHandler(parrot);
The MOQ (pronounced "Mock You") framework allows us to fabricate a different implementation for IFileWriting when testing GermanTranslatorWrapper.cs. Instead of facilitating File I/O stuff, our IFileWriting will just be a dummy object that does nothing. MOQ allows us to specify what the one method inside of the interface will do. I have it just returning true without attempting any file stuff. The one test in MockTest.cs shows off mocking, the making of an on-the-fly interface that is a stand-in for an interface that would otherwise be procured through an upcasting from a class. Again, we can only sidestep the external dependency in GermanTranslatorWrapper.cs in testing by way of the manner GermanTranslatorWrapper.cs was written to begin with. - Another way to do what is done in MockTest.cs is shown in StubTest.cs. Here we do not use a mocking framework, but instead we just hand in a preexisting object (MockWithMe.cs) to be upcast to IFileWriting. In the mocking approach we have to use interfaces, but not so with stub objects. Stubs can be more flexible in this manner while mocks can be quicker to craft and easier to craft in variations.
No comments:
Post a Comment