The Challenge
You have a Compact Framework application to write. You've chosen to make it a rich client
(Windows Forms), and you want a suite of automated tests covering the code.
Passive View
I investigated the options for testing directly on the Compact Framework itself, but this turns out to be tricky
and ties you to running inside the emulator (a virtual machine).
However the Compact Framework uses a .Net feature called
retargeting.
This allows your Compact Framework code to be run in the full .Net environment as long as it doesn't reference
any classes that are unique to the Compact Framework (e.g., the SIP).
If you use Passive View,
you can take your controller class and test it in the regular .Net
Framework using NUnit leaving only a tiny bit of code in the View that is specific to the
Compact Framework.
Creating the View
In the example code below I created a simple GUI that would show or hide a message,
allow the user to change the colour of the message from a drop-down, and would prompt
the user for confirmation before hiding the message. This provides a simple enough example
to cover in a blog, while being complex enough to demonstrate several useful techniques
for testing using Passive View.
The view can be created directly in Visual Studio (with the Window Mobile 6 SDK installed).
The designer gives you an excellent rendition of a PDA client making it easy to design a
user interface (notwithstanding that WPF would have been nicer - but hasn't been implemented
on the Compact Framework yet).
To create the view:
- Create a Form;
- Create the controls;
- Name the controls, and mark them with the modifier 'Public';
- Wire up a controller when the View is instantiated.
To wire up a controller, create a controller class (which is going to hold all the
application's presentation logic), and construct it when the View is instantiated.
internal class MainController
{
private MainView _view;
public MainController(MainView view)
{
_view = view;
...
public partial class MainView : Form
{
public MainView()
{
InitializeComponent();
new MainController(this);
...
And that's it. No other logic should exist in the View.
Handling User Input
Most of the user input can be handled directly on the View in the tests.
To simulate someone typing into a text-box, simply set the Text property.
To simulate someone making a selection from a drop-down, set the SelectedIndex
property.
Simulating a button 'click' requires a tiny bit of reflection to invoke the protected method:
private void Click(Button button)
{
if (!button.Enabled)
Assert.Fail("Attempt to click button '"
+ button.Text
+ "' while it is not enabled");
MethodInfo onClick = button.GetType()
.GetMethod("OnClick",
BindingFlags.NonPublic
| BindingFlags.Instance);
onClick.Invoke(button, new object[] { null });
}
You end up with a fairly readable test:
[Test]
public void Test_WhenShowMessageIsClicked_Then_…
MessageIsDisplayed_And_ColourSelectionIsPopulated()
{
MainView view = new TestableView();
Click(view.ShowMessage);
Assert.AreEqual(false,
view.ShowMessage.Enabled);
...
Handling the Visible Property
You can set most properties directly on the real framework classes. However,
some properties don't always behave correctly outside of the Windows Forms runtime.
The Visible property is one such case.
You could mock/stub the Visible property on the controls, however typically these
might get called many times in a single test; mocking could make your tests very brittle.
Alternatively you could have an adapter/proxy
for each of the controls that let you simulate events/properties.
I find it easier to have a fake implementation I can use just inside the tests.
In this example I wrapped all calls to the Visible property with a (virtual) method:
public partial class MainView : Form
{
...
public virtual void SetVisible(
Control control,
bool isVisible)
{
control.Visible = isVisible;
}
public virtual bool IsVisible(Control control)
{
return control.Visible;
}
...
This was replaced with a fake implementation in the tests:
public class TestableView : MainView
{
...
private Dictionary<Control, bool>
_controlVisibility
= new Dictionary<Control, bool>();
public override void SetVisible(
Control control,
bool isVisible)
{
_controlVisibility[control] = isVisible;
}
public override bool IsVisible(Control control)
{
if (!_controlVisibility.ContainsKey(control))
Assert.Fail("Control '" + control.Name
+ "' visibility has not been set by…
SetVisible()");
return _controlVisibility[control];
}
...
Handling User Interaction
Another thing that needs to be tested is user interaction (e.g., clicking yes/no
on a message box). While you could use a test double/stub
for this, it does tend get a little tedious (you typically end up with a different stub method for every test).
It is better to Mock the user input by (in this case) creating a proxy to the message box class instead of going to
the (hard to test) static methods directly:
public class DialogHandler
{
public virtual DialogResult ShowMessageBox(
string text,
string caption,
MessageBoxButtons buttons,
MessageBoxIcon icon,
MessageBoxDefaultButton defaultButton)
...
In this example I've used the excellent
Rhino Mocks
to mock the DialogHandler implementation.
So you can set expectations, and their responses, directly in the tests:
[Test]
public void Test_WhenHideMessageIsClicked_…
AndUserConfirms_Then_MessageIsHidden()
{
MockRepository mocks = new MockRepository();
DialogHandler dialogHandler =
mocks.StrictMock<DialogHandler>();
MainView view = new TestableView(dialogHandler);
Expect
.Call(dialogHandler
.ShowMessageBox(
"Are you sure?",
"Check",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1))
.Return(DialogResult.Yes);
mocks.ReplayAll();
Click(view.ShowMessage);
Click(view.HideMessage);
mocks.VerifyAll();
...
Summary
Now you have the highest possible test-coverage on your presentation layer, without resorting to
(heavyweight) full integration/acceptance tests.
The example code (link below) demonstrates each of the above sections using:
-
NAnt
and
NUnit
- to automate the build and tests;
-
Retargeting
- to allow the Compact Framework classes to be tested inside the regular .Net Framework;
-
Passive View
- to allow testing of as much of the application as possible while not requiring the Windows
Forms runtime;
-
Test Double
- (specifically a
fake
) to allow testing of properties that normally only work in the Windows Forms runtime;
-
Mocks
- using the excellent
Rhino Mocks
to test user interaction in the tests.
Worth noting is that there is nothing here that is special to the Compact Framework. I would use
exactly the same technique to test any presentation layer (e.g., WPF/Silverlight).
The Compact Framework is merely a useful
demonstration of where the real runtime is not easy to use, and these design patterns
and testing techniques shine.
Passive View Demo
download, unzip, run CommandPrompt.bat, and type 'NAnt'