At the end of Test Driven Body Snatchers I promised to follow up with a walkthrough of real-world Test Driven Development. What gets into me?
Well, a promise is a promise.
Walking Through Test Driven Development
The set up is this: We’ve designed the Silverlight HyperVideo Platform using the MVVM pattern.
We’re going to take a trip in the way-back machine, and design the ItemsViewModel class (the VM in MVVM) using Test-Driven Development.
Begin At The Beginning…
We start out by examining the existing ItemsView class (the V in MVVM ) which is implemented in ItemsView.xaml (and its code-behind):
<UserControl x:Class="SilverlightHVP.View.ItemsView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="White"> <ListBox Name="ItemsList" Background="Beige" Width="200" Height="250" HorizontalAlignment="Center" VerticalAlignment="Top" ItemsSource="{Binding Items}" SelectedItem="{Binding CurrentItem, Mode=TwoWay}" DisplayMemberPath="TextToDisplay"> </ListBox> </Grid> </UserControl>
Designing the View Model
We are now ready to create the ItemsViewModel, which will serve as the data-context for the binding shown in the ListBox.
And here the world turns upside down.
In the absence of test-driven development, I’d think about this a lot and make all sorts of decisions before writing a line of code. I’d decide, no doubt that the ItemsViewModel needs to provide a collection of Item objects to serve as the ItemsSource, and thus the Items property of the class would be a collection of Item objects.
Since CurrentItem is bound to the SelectedItem, I’d try to figure out if that is a property with its backing value held in the ItemsViewModel or in, for example, the state object that is shared by all the ViewModel classes.
You can do all that, and you can make unit tests for it, but test-driven development works differently. In TDD you make much smaller design decisions, write much smaller bits of code, and create a rapid cycle of three steps:
- Determine one key thing you believe the ItemsViewModel class must provide and write a test asserting that it does provide that. Run that test. It will fail.
- Write just enough code in ItemsViewModel to make the test pass. No stop, just enough, no more. Good. Run the test again, it should pass.
- Repeat*
* Old joke: a programmer was stuck in the shower because the shampoo bottle said “apply, lather, rinse, repeat.” |
The Discipline of TDD
Yes, it is infuriating to write the simplest thing when you believe you already know more. But a funny thing happens… you find that you are never lost, confused, befuddled or even in need of a debugger. You only did a little bit and the test either passes or it fails. If it fails, it is trivial to figure out what is wrong; after all you probably only wrote a couple lines of code.
Creating The Test in Visual Studio
To get started, I’ll add a project to the SilverlightHVP solution of type Test Project and I’ll name it SilverlightHVP.Tests.
Visual Studio will add the project, add a file UnitTest1.cs and add two new files to the solution: Local.testsettings and TraceAndTestImpact.testsettings. It also adds a reference to Microsoft.VisualStudio.Quality.
Tools.UnitTestFramework. Once the project is added to the solution, I can right click on the new project and choose Add->NewTest, bringing up the Add New Test dialog box. I recommend experimenting with each of the tests offered, but for now, I’ll just choose Basic Unit Test and name it ItemsViewModelUnitTests.
Visual Studio will create the class and a first test method, and adorn each with the appropriate attributes. Delete TestMethod1 and it is time to write (and name!) our first test method.
The First Test
So; what is the very least we think the ItemsViewModel must do? Let’s return to the ViewModel and note that the ListBox.ItemsSource property is binding to a ItemsViewModel property named Items.
A quick look at the Silverlight documentation reveals that the ListBox.ItemsSource property is of type IEnumerable. That is, a collection. We don’t yet know what is in the collection, but we do know that the ItemsViewModel must have a property Items that returns an IEnumerable of something. Let’s create a test for that.
So what is the test actually testing for? The minimum is that the Items property, an IEnumerable<object> (that is, an enumerable list of something) is not null.
Our naming convention is to identify the thing we are testing and what should be true. The name, therefore, is ItemsProperty_ShouldNotBeNull.
C#
[TestMethod] public void ItemsProperty_ShouldNotBeNull() { }
VB.Net
<TESTMETHOD> Public Sub ItemsProperty_ShouldNotBeNull() End Sub</TESTMETHOD>
Creating the body of the test requires testing the property of an ItemsViewModel instance; so the first line of the test will create the beast.
When we attempt to make an instance, we immediately get the feedback that something is amiss; no surprise; the class doesn’t exist yet. Visual Studio is ever-helpful, however, and it offers to generate the classs for you. Let’s have it do that. Hey! Presto! a new file ItemsViewModel.cs /ItemsViewModel.vb is generated in the test project. That’s not where we’ll want it, but for now it is just fine.
Our first test line no longer shows an error, and oh, by the way, creating this line was a forcing function on the design: we needed a class of type ItemsViewModel. Similarly, we now will attempt to retrieve the Items property as an enumerable, which of courses will generate both an error (no such property) and an offer to create the property for you. Once again we’ll let Visual Studio generate the needed stub. With that, we can test that Items is not null:
Place your cursor on the name of the test method and press Control-R, T; this will run this one test and display the results in a window, as shown.
Our dreams and ambitions are fulfilled; the test failed as we expected it to! Great.
(click on images for full size)
Making The Code Pass The Test
Now we write just enough code for the test to pass. Watch it! Just enough and no more.
C#
class ItemsViewModel { public IEnumerable<object> Items { get { return new List<Object>(); } set { } } }
VB.Net
Friend Class ItemsViewModel Public Property Items() As IEnumerable(Of Object) Get Return New List(Of Object)() From {New Object()} End Get Set(ByVal value As IEnumerable(Of Object)) End Set End Property End Class
Since I can’t instantiate an interface, I chose a very simple class that implements IEnumerable. Notice that I’ve not populated the list; all I’ve done is write enough code to satisfy the test. This is pedantic, rigid, annoying and incredibly useful. The design emerges based on the needs, the needs are expressed by the tests, and the code never goes beyond the (thought through and tested) requirements.
A real though not driving benefit is that the tests become both insurance and documentation; and unlike other documentation, as long as you “rigidly” make sure you run all the tests every time you make any change, the documentation never goes out of date.
The Second Test
At this point we know that we want more than for the property to be non-null; we want it to have items. Let’s test for that:
C#
[TestMethod] public void ItemsProperty_ShouldNotBeNull() { var itemsViewModel = new ItemsViewModel(); IEnumerable<object> items = itemsViewModel.Items; Assert.IsNotNull( items ); } [TestMethod] public void ItemsProperty_ShouldReturnListOfObjects() { var itemsViewModel = new ItemsViewModel(); IEnumerable<object> items = itemsViewModel.Items; Assert.AreNotEqual<int>( 0, items.Count<object>() ); }
VB.NET
<TESTMETHOD> Public Sub ItemsProperty_ShouldNotBeNull() Dim itemsViewModel = New ItemsViewModel() Dim items As IEnumerable(Of Object) = itemsViewModel.Items Assert.IsNotNull(items) End Sub </TESTMETHOD><TESTMETHOD> Public Sub ItemsProperty_ShouldReturnListOfObjects() Dim itemsViewModel = New ItemsViewModel() Dim items As IEnumerable(Of Object) = itemsViewModel.Items Assert.AreNotEqual(Of Integer)(0, items.Count(Of Object)()) End Sub</TESTMETHOD>
Running Two Tests
You can run the second test just as you ran the first, but we really want to make sure that it is wicked easy to run all the tests every time. Click in the class, but not on either test, and press Ctrl-R, T. This causes both tests to be run, and sure enough the first test (ShouldNotBeNull) passes, but the new test fails.
Once again, we write just enough code to make this pass.
C#
class ItemsViewModel { public IEnumerable<object> Items { get { return new List<Object>() { new Object() }; } set { } } }
VB.Net
Friend Class ItemsViewModel Public Property Items() As IEnumerable(Of Object) Get Return New List(Of Object)() From {New Object()} End Get Set(ByVal value As IEnumerable(Of Object)) End Set End Property End Class
And running the tests again, both pass. And so it goes. Step by tiny step.
Factor Out Common Code In Tests?
Most of you are probably starting to itch terribly, as you look at the two tests and note that the first two (of three!) lines are identical. Yikes! Somebody factor that out, quick!
And we will, but before we do, let me note that this is not a given; in fact doing so is somewhat controversial. First, how to do it, then the controversy.
C#
[TestClass] public class ItemsViewModelTests { private IEnumerable<object> items; [TestInitialize] public void ItemsProperty_SetUp() { var itemsViewModel = new ItemsViewModel(); items = itemsViewModel.Items; } [TestMethod] public void ItemsProperty_ShouldNotBeNull() { Assert.IsNotNull( items ); } [TestMethod] public void ItemsProperty_ShouldReturnListOfObjects() { Assert.AreNotEqual<int>( 0, items.Count<object>() ); } [TestCleanup] public void ItemsProperty_CleanUp() { items = null; } }
VB.Net
<TESTCLASS> Public Class ItemsViewModelTests Private items As IEnumerable(Of Object) <TESTINITIALIZE> Public Sub ItemsProperty_SetUp() Dim itemsViewModel = New ItemsViewModel() items = itemsViewModel.Items End Sub <TESTMETHOD> Public Sub ItemsProperty_ShouldNotBeNull() Assert.IsNotNull(items) End Sub </TESTMETHOD><TESTMETHOD> Public Sub ItemsProperty_ShouldReturnListOfObjects() Assert.AreNotEqual(Of Integer)(0, items.Count(Of Object)()) End Sub <TESTCLEANUP> Public Sub ItemsProperty_CleanUp() items = Nothing End Sub End Class</TESTCLEANUP></TESTMETHOD></TESTINITIALIZE></TESTCLASS>
The trick here is that the testing framework will run each test in the following sequence:
- If there is a method marked [TestInitialize] run that first
- Run the test method
- If there is a method marked [TestCleanup] run that last
The controversy is this: you do not want any dependencies between tests. It can never be allowed that running one test affects another. That is, for example, ShouldReturnListOfObjects should return the same results if it is run before or after ShouldNotBeNull or even if ShouldNotBeNull is not run at all.
One way to protect yourself is to have each test be fully self-contained. A second way is to ensure that CleanUp eliminates any side-effects.
Or Not…
James Newkirk, one of the brighter lights in the field, author of nUnit and xUnit, explains in some detail why he opposes using Setup and Cleanup (TearDown) methods.
I’ve taken the liberty® of quoting one of the key paragraphs:
The problem that I have with SetUp in this case is twofold. The first and primary complaint is that when I am reading each test I have to glance up to BeforeTest() to see the values that are being used in the test. Worse yet if there was a TearDown method I would need to look in 3 methods. The second issue is that BeforeTest() initializes member variables for all 3 tests which complicates BeforeTest() and makes it violate the single responsibility pattern.
Brad Wilson, Newkirk’s partner in crime, elucidates four objections to the use of setup and teardown, which I summarize here,
- You can’t tell what the test is doing without looking at the setup, which can be far away in the file when you have a number of tests
- If the setup or teardown throws an exception it can be very confusing as to what is wrong
- There is only one setup and teardown per set of tests, and that presumes every test needs the same setup; a dangerous and misleading assumption that makes the test harder to understand.
- It is too easy to have the setup code change the environment for tests run later in the series; breaking the rule that tests are independent.
==========
I am incredibly grateful to Abby Fichtner for helping me past the initial hurdles in Test Driven Development (though she may argue I’m not yet past them!).
She recommended to me, and I recommend to you this excellent tutorial by Jason Olson from Channel 9 (where you can now watch Silverlight TV)
This is really good but can we have auto generation of Test methods from silverlight project
Thanks, very timely as I am beginning down the TDD road myself. First, I think I agree with NOT using the SetUp and TearDown methods – at first it irked me to constantly repeat the setup code, but these objections amke sens when you might easily have hundreds of tests in your test class.
Two small nitpicks if I may:
1) In “Designing the ViewModel” section where you list the 3 steps, #3 should actually be “Refactor”, then #4 could be “Repeat”. The goal being that the initial code is just to make it work, the refactor is to make it work better.
2) In “Running Two Test” you break the rule of writing only the code that you need: you have an empty Setter block that is completely unnecessary. You shouldn’t add it until you need it 🙂
Thanks again, this article was very helpful!
Thanks! Actually nitpick #1 is no nitpick, it is a mistake on my part, and a good catch on yours.
Thanks for the writeup Jesse. I just walked through it because I hadn’t used the ‘Test Project’ project type before. I noticed that once I move the ItemsViewModel outside of the Test Project and into a Silverlight Class Library I could no longer use the tests I had written against it, because I can’t add a reference to a Silverlight Class Library from a Test Project. There error is:
“The project ‘Infrastructure’ cannot be referenced. The referenced project is targeted to a different framework family (Silverlight)”
As best I know, you have to use the Silverlight Unit Test Framework for writing and running any tests for Silverlight libraries. The ‘Silverlight Unit Test Application’ project template comes with the Silverlight Toolkit and moving my tests into that let me add a reference to my SL library and my tests seemed to work fine.
That is really good information. I’ll try to work on verifying that, but I have to say that I’ve really started using the SLUT pretty much exclusively (if only for the acronym!)