Sunday 8 February 2009

WPF Testing - Part I

Automating GUI testing is always a tricky business. In fact its so hard that people tend to mark it off as too hard to bother with. I believe however that this observation is not correct.

During one of my consulting gigs I was starting to instruct a new team on the concepts of unit tests and since they working on a real product they actually have some GUI involved (imagine that).If that's not enough they are using WPF as their framework of choice. I on the other side cant claim to be a GUI expert (to say the least) and my hands on experience with actual WPF code is, well, close to nothing.

I couldn't leave it at that, so I invested some time in actually trying to figure out how one can write tests for WPF based GUI. This post (which hopefully will be followed by some more) will demonstrate my finding on the matter.

First scenario I wanted to test was how I can trigger an event using code instead of a person clicking on the GUI. For that (and probably for the following examples as well) I constructed a small application that looks like this:

mainWindow

As you can see for now I have a big empty window with a single button and some text fields, but for now hat will be enough. In order to achieve what I'm doing I'll be using the Isolator framework, and I will try keeping my design as simple as I can (I know that there are many patterns out there that probably reflect a much better design scheme, but I really like to address the pressing need of the common programmer and not resort to fancy designs patterns at this point)

Firing a mocked Event

As I said, first I want to explore how to test an event handler wired to a button. i.e. to simulate a user pressing a button and check that the correct logic is executed. for the purpose of this demo I assume that my GUI is backed up by a business logic layer that will handle the actual business logic, and since that should be tested separately,  all I want for now is to make sure that the proper logic from that layer is called.

so here's the implementation of the business logic:

internal class BusinessLogic
{
void GetResult()
{
throw new NotImplementedException
("no logic yet");
}
}
As you can see noting much in there, in fact since I'm not interested in the buisness layer at this point the implemantation is not yet ready and all i have is a an empty

and here is the the window source code:

public partial class HurtFundsMainWindow : Window
{
public HurtFundsMainWindow()
{
InitializeComponent();
}

private void GetResults_Click(
object sender, RoutedEventArgs e)
{
BusinessLogic logic = new BusinessLogic();
logic.GetResult();
}
}

Again at this point i dont have much, only a simple event handler for the button click action that will delegate all logic into the business layer. Note that i'm not using any dependancy injection pattern and I'm not working against an interface at this point. (my application is so simple that I really don't see the need for doing that. I assume that when the application evolves the need will make me evolve the design as well)

and here is the actual test code:

[TestMethod]
[Isolated]
public void Clicking()
{
//Create the fake business logic and inject it into production code
BusinessLogic fakeLogic =
Isolate.Fake.Instance<BusinessLogic>();
Isolate.Swap.NextInstance<BusinessLogic>()
.With(fakeLogic);

//Create the fake button
Button fakeButton =
Isolate.Fake.Instance<Button>(Members.CallOriginal);
//inject it into production code
Isolate.Swap.NextInstance<Button>()
.With(fakeButton);
//replace the button event wiring
Mock mockButton =
MockManager.GetMockOf(fakeButton);
MockedEvent handle =
mockButton.ExpectAddEvent("Click");

//create the window
HurtFundsMainWindow wind =
new HurtFundsMainWindow();

//fire the mock event
handle.Fire(wind, new RoutedEventArgs());

Isolate.Verify.WasCalledWithAnyArguments(
() => fakeLogic.GetResult());
}

Not very trivial so lets see what's goes on here.

the first step in the test, is creation of the fake business logic, i create the fakeLogic and using the Swap ability I ask the Isolator framework to replace the next created instance of business logic with the fakeLogic (check this for further details).

Second step is doing the same for the button that will be created, after that I also fake the actual event wiring done by WPF and put my hands on a faked handle which I later use to fire the event. (note the event handling is done using an older variant of the Isolator API. I hope that in the near future the proper API will be added to the AAA syntax as well)

Next comes the actual executing. I first create the window, and then I use the fake handle to trigger the event.

At the end of the test I verify that the GUI actually calls the proper logic method (in this case the GetResult() method).



That's all for now, while this is only a basic scenario, the principles shown here can be adapted to handle much of the event system of the WPF system. If you have any questions feel free to leave a comment.

2 comments:

Soon Hui said...

Hi, it seems to me that you are testing that the click event actually fires the correct class and method... I think this is a bit like unit testing the View part of a MVC model, and a bit too trivial. IMHO, it's much better to test whether fakeLogic.GetResult() returns the correct result and skip the click testing part.

Lior Friedman: said...

Actually thats exactly what I'm doing. Surprisingly enough the post was written as an answer to a concrete question on how to do exactly that, so at least some guys find this worth testing. In any case this does not replace testing the Controller parts just an addition to them.

I also think that while this example is very simplistic, this technique is good for:
1) Testing the whole path from view to controller in a single test.
2) There are scenarios in which the "view" has some GUI logic in it. logic that make more sense to test.
(for example think of a scenario in which a "cancel" button pops an "are you sure?" dialog)

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Walgreens Printable Coupons