I use NUnit for my
automated tests. Because of that, all my
tests are unit tests, right?
WRONG! The name of
the testing framework has no bearing on the type of test you have. NUnit is a framework for running automated
tests. You _can_ write unit tests with
it, but you can also write integration tests as well as full-system tests with
it. A
unit test is a special type of developer test and can be done with or without
NUnit.
A unit test tests a
single line of code.
How big is a unit?
Well, that’s up to you, and there is not scientific answer. Typically, you only give a class a single
responsibility. The class may have several
methods since the class may need to do several things to accomplish the single
responsibility. The class may have to
collaborate with several other classes to accomplish its purpose. A unit of code is an identifiable chunk of
code needed to accomplish part of a responsibility. Is that vague enough for you? In my example below, I’ll clear this up a
bit.
A unit test isolates
the code being tested.
A class will need to talk to other classes. That’s a given. Sometimes this is ok for testing, and
sometimes this just gets in the way. It
might be ok to talk to a class that just builds a string (like StringBuilder),
but it’s not ok to talk to a class that grabs information from a configuration
file. In a unit test, you need to take
environmental dependencies out of the equation so that a pass or failure is
truly dependent on the code being tested.
You don’t want the test failing because the configuration file wasn’t in
the right spot. There are plenty of
techniques available for this. To start,
you need to code against interfaces and use fake objects like stubs and
mocks. I like the Rhino mock framework
for this.
Here are some
dependencies that will need to be frequently simulated for unit testing:
- Config
files - Registry
values - Databases
- Environment
variables - Machine
name - System
clock (Yup. Even that has the
potential to get in your way).
Example:
This example will show a real web user control that I’ve
unit-tested. This is not
theoretical. This is inside my EZWeb
software. The purpose of the following
screen is to maintain a few pieces of information for the page being
viewed. The user can set the title of
the page and some other things.
I’ve used the Mode-View-Presenter pattern to make unit
testing this easier. Obviously if all my
code is in the code-behind of the ASCX, then I won’t be able to test any of it
because I can’t run that code outside of the ASP.NET runtime. If you aren’t familiar with the MVP pattern
now, take some time to read up on it.
The presenter is the controlling class that will be tested. The code-behind becomes very dumb. The code-behind will implement my view
interface and be responsible for taking information and setting the correct
control. The view is very small, and all
the intelligence is in the controlling class (the Presenter). The presenter is where the bugs will hide, so
I’ll unit test that class. The model is
represented by an interface IPageConfig that you’ll see being referenced.
The following example
shows a unit test of the code that gets data from the model and publishes it to
the view. The textboxes and drop-downs
need to be set properly. This is not the
full code. The full code also reacts to
the save button being clicked and taking modifying information and saving it.
Here is the view interface:
namespace
Palermo.EZWeb.UI
{
public interface IPagePropertiesView
{
string
Title { get; set;
}
bool HasParent
{ get; set; }
string
Template { get; set;
}
string
Theme { get; set;
}
string
Plugin { get; set;
}
string
Parameter { get; set;
}
bool
IsPostback { get; }
DictionaryList
GetTemplateChoices();
void SetTemplatesDropDown(DictionaryList
list);
DictionaryList
GetThemeChoices();
void
SetThemesDropDown(DictionaryList list);
DictionaryList
GetPluginChoices();
void
SetPluginDropDown(DictionaryList list);
void
EnableTitle(bool enabled);
void
EnableTemplate(bool enabled);
void
EnableTheme(bool enabled);
void
EnablePlugin(bool enabled);
void
EnableParameter(bool enabled);
void
ReloadParent();
}
}
Here is the
code-behind that implements the view interface (truncated):
public partial class PageAdministration : UserControl,
IPlugin, IPagePropertiesView
{
private
PagePropertiesPresenter _presenter;
public string Title
{
get
{ return txtTitle.Text; }
set
{ txtTitle.Text = value; }
}
public bool HasParent
{
get
{ return Convert.ToBoolean(ddlHasParent.SelectedValue);
}
set
{ ddlHasParent.SelectedValue = value.ToString();
}
}
public string Template
{
get
{ return ddlTemplate.SelectedValue; }
set
{ ddlTemplate.SelectedValue = value; }
}
. . .
Here is the part of
the Presenter that we’ll be focusing on:
public class PagePropertiesPresenter
{
private
readonly IPagePropertiesView
_view;
private
ICurrentContext _context;
public
PagePropertiesPresenter(IPagePropertiesView
view)
{
_view = view;
_context = (ICurrentContext) ObjectFactory.GetInstance(typeof (ICurrentContext));
}
//testing
constructor
public PagePropertiesPresenter(IPagePropertiesView view, ICurrentContext
context)
{
_view = view;
_context = context;
}
public virtual void
LoadConfiguration()
{
if
(!_view.IsPostback)
{
IPageConfig
config = _context.GetPageConfig();
_view.Title = config.Title;
_view.HasParent =
config.HasParent;
DictionaryList
templates = removeBadItems(_view.GetTemplateChoices());
_view.SetTemplatesDropDown(templates);
_view.Template =
config.Template;
DictionaryList
themes = removeBadItems(_view.GetThemeChoices());
_view.SetThemesDropDown(themes);
_view.Theme = config.Theme;
DictionaryList
plugins = removeBadItems(_view.GetPluginChoices());
_view.SetPluginDropDown(plugins);
_view.Plugin = config.Plugin;
_view.Parameter = config.Parameter;
}
}
. . .
Notice that the LoadConfiguration() method checks for
postback (through the view) and then uses the MODEL to set pieces of
information on the VIEW. You may think
that this code is boring, but it’s essential for the behavior of the screen.
Now for the
test. Note that we’re simulating the
view and the ICurrentContext interface since these are collaborators. The ICurrentContext provides the MODEL to the
Presenter:
[TestFixture]
public class PagePropertiesPresenterTester
{
[Test]
public void ShouldSetAllInformationOnPage()
{
MockRepository
mocks = new MockRepository();
IPageConfig
mockConfig = (IPageConfig)mocks.CreateMock(typeof(IPageConfig));
IPagePropertiesView
view = (IPagePropertiesView)mocks.CreateMock(typeof(IPagePropertiesView));
ICurrentContext
context = (ICurrentContext)mocks.CreateMock(typeof(ICurrentContext));
Expect.Call(view.IsPostback).Return(false);
Expect.Call(context.GetPageConfig()).Return(mockConfig);
string
title = “fake title”;
Expect.Call(mockConfig.Title).Return(title);
view.Title = title;
bool
hasParent = false;
Expect.Call(mockConfig.HasParent).Return(hasParent);
view.HasParent = hasParent;
string
selectedItem = “foo”;
Expect.Call(mockConfig.Template).Return(selectedItem);
DictionaryList
list = new DictionaryList();
list.Add(“first”,
“first”);
list.Add(“Foo”,
selectedItem);
list.Add(“.svn”,
“.svn”);
list.Add(“_something”,
“_something”);
Expect.Call(view.GetTemplateChoices()).Return(list);
view.SetTemplatesDropDown(null);
LastCall.On(view).Constraints(new PropertyIs(“Count”, 2));//.
and _ should be stripped out.
view.Template = selectedItem;
Expect.Call(mockConfig.Theme).Return(selectedItem);
Expect.Call(view.GetThemeChoices()).Return(list);
view.SetThemesDropDown(null);
LastCall.On(view).Constraints(new PropertyIs(“Count”, 2));
view.Theme = selectedItem;
Expect.Call(mockConfig.Plugin).Return(selectedItem);
Expect.Call(view.GetPluginChoices()).Return(list);
view.SetPluginDropDown(null);
LastCall.On(view).Constraints(new PropertyIs(“Count”, 2));
view.Plugin = selectedItem;
string
fakeParameter = “faky”;
Expect.Call(mockConfig.Parameter).Return(fakeParameter);
view.Parameter = fakeParameter;
mocks.ReplayAll();
PagePropertiesPresenter
presenter = new PagePropertiesPresenter(view,
context);
presenter.LoadConfiguration();
mocks.VerifyAll();
}
. . .
Your first thought might be that this unit test method is
too long. It certainly pushes my comfort
level as well. I could have chosen a
small one for this example, but I chose my largest one instead. I’ve seen other unit testing examples that
are so trivial that they don’t demonstrate much. In this sample, I chose one of the most
difficult things to unit test: A UI
screen. Notice that I’m using Rhino
mocks to set up my fake objects. The call
to “mocks.VerifyAll()” does a check to ensure that the collaborators were
called with the correct input. After all,
my presenter method is in charge of getting information from the MODEL and
publishing them to the VIEW. If you
spend some time going over this test, you can see some of the rules the code
has to live by. One of the side-effects
of the unit test is documentation of the code (developer documentation). At this point, I can refactor my method
knowing that I have this test as a safety net.
How do I unit test my
legacy code?
Change it. This
screen started out several years ago with all the code in the code-behind
class. It was impossible to test this
way. I had to refactor to the MVP
pattern to enable testing. I had to
break some things away by inserting an interface so that I’d have a seam to
break dependencies. In short, you must
refactor your existing code to get it to a point where it is testable. The reason it’s not testable is that it’s
tightly-coupled with its dependencies. I
hope by now that the words “loosely-coupled” are recognized as “good” and “tightly-coupled”
are recognized as “bad”. Testable code
is loosely-coupled. Loosely-coupled code
is testable. And now the big leap: Testable code == good.