Behaviour driven development on user interfaces with Automation peer

Testing user interfaces is usually a pain in the butt. No matter how cool your UI is, it will often boil down to a brain-cell killing process clicking the same buttons over and over again. Thanks to the Geek Gods of .Net though, there are ways to automate this, and it’s not even that tough to do.

In this post, we’re going to use Automation peers to expose a button to a unit test, and have the test start the application, click the button, and confirm that the button does what it’s meant to do. We’re going to use MbUnit to write the test, and NBehave to make the tests nice and clear.

The code used in this post can be downloaded here.

A quick confession about BDD

Ok, so. The application we’re going to use for this test is the file organizer described in one of my previous posts. This would normally mean I’m being a bad boy because I’m turning Behaviour driven development on its head and writing the tests after I’ve written the application. Mea culpa; I’ve seen the error of my ways though… the organizer is due for a bit of refactoring (I’m planning to clean it up and use Marlon’s advice on command bindings, among other things) so I’m writing some tests to make sure it still works after that, like a good boy. Stop looking at me like that.

We’ll get back to what BDD is and is not in a little while, but first, let’s look at the cool sparkly toy that makes UI testing possible.

Getting started with Automation peers

Automation peers are helper objects that expose controls to other applications, like usability applications and test applications. They provide means to find controls, and an abstraction layer to allow us to interact with them. If you ever tried to automate a UI test using reflection, you should have a rough idea how much work these peers save you. If not… don’t try it, trust me.

Every standard control in WPF has an automation peer. Custom controls may or may not have a peer; that depends on whether the provider has implemented one or otherwise. We’re going to stick with standard controls for now, so we don’t have anything to worry about.

Labelling our controls

We’re going to make our unit test open our application, find the close button, and click it. To do this, we need to label it in such a way that we can find it. Now, we could use a key, a name, or whatever, but we’re going to do this by the book and use the AutomationId dependency property. This is the standard property used to identify controls in this manner, so if later on we (or anyone else really) want to write a helper application later on, we know how we’ll find it.

To label the close button, we’ll go into Window1.xaml in the FileOrganizer project, and add:

   26: <Button
   27:     Margin="0,0,0.2,20"
   28:     Grid.Column="1"
   29:     Grid.Row="0"
   30:     Width="45"
   31:     Style="{StaticResource CloseButton}"
   32:     Content="x"
   33:     Click="Button_Click"
   34:     AutomationProperties.AutomationId="close"/>

Note: You can inspect applications with UISpy to determine what ids the application exposes.

Getting an AutomationElement from a running application

We want to exercise the UI in the context of a running application, so it makes sense to have the test run the application and hook into it. This is as simple as using Process.Start to run the executable. Once the application is running, we can get the automation element for the window by requesting it by name.

   1: return AutomationElement
   2:     .RootElement
   3:     .FindFirst(TreeScope.Children,
   4:         new PropertyCondition(AutomationElement.NameProperty, name));

AutomationElement.RootElement represents the top level of all applications; the desktop. We can use the FindFirst method to locate the first object matching the given property. The TreeScope.Children argument tells the method to look at the direct children of the root element only. If an object matching the same condition is available further down in the tree, we want to ignore it, because we’re specifically looking for a top level window.

The PropertyCondition we’re passing in tells FindFirst that we want objects whose name matches the given value. In this case, we pass in the title of the window.

You will notice that I’ve allowed a small delay between the start of the process, and the collection of the AutomationElement. This gives the application time to start up, otherwise the test will try to pick up element before it’s created, and go boom.

Once we’ve got an instance of the AutomationElement, we can start fiddling with the controls.

Push that button

To get the element for the button, we’re going to use exactly the same technique as above. This time we’re going to look at the window’s automation element, and ask it to give us the first element whose automation id is “close”:

   1: closeButton = instance.FindFirst(TreeScope.Descendants,
   2:     new PropertyCondition(AutomationElement.AutomationIdProperty, id));

Notice how we’re using TreeScope.Descendants now, rather than TreeScope.Children. This time we don’t care where the element is in the hierarchy, so we’ll let the method look as deep as it likes until it finds what it’s looking for.

To interact with the button, we’ll ask the element to give us its InvokePattern instance. Automation elements expose a number of these patterns (see “UI Automation Control Patterns Overview” for more information) which abstract the specific interactions for us. For example the invoke pattern “Represents controls that initiate or perform a single, unambiguous action and do not maintain state when activated.”, so we don’t have to worry whether the control is activated exactly. Other patterns, such as TextPattern, let us extract information from a control. To get a pattern:

   1: InvokePattern closeButtonInvoker =
   2:     closeButton.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;

We can then call .Invoke() on the InvokePattern to simulate a click.

How this fits in with BDD

Behaviour driven development is an evolution of Test driven development which I found to be extremely interesting. Rather than focusing on units of code, the BDD approach is to focus on a specification, and test against that. Specifications can be as intricate as required, but they map perfectly to use cases. Not only does this mean that we can write tests that are focused on business value, but we can also generate tests that are a lot more readable. Since we start from a spec, I also found it easier to follow. With classic TDD, I often find myself stuck with a blank screen thinking of where to start. With a well defined structure to follow, there’s a lot less temptation to wade in and hack first, test later.

A spec for this test

This test is to be stupidly simple, so the spec for it wouldn’t be that complex:

Given that the application is running

When the close button is pressed

Then the application should stop running

This gives us the basic structure for our test; given [some precondition], when [some things are done], then [we expect these other things to happen]. From that starting point, you could write a unit test using whatever framework you want, and it would be good. However, we’ll go a bit further and add NBehave in. NBehave allows us to support exactly the same structure in code, and creates some reports for us to boot.

The same spec, in code:

   1: [Theme("Main screen interaction"), TestFixture]
   2: public class MainUserInterfaceTests
   3: {
   4:     Story story = new Story("Close application");
   5:
   6:     story
   7:         .AsA("User")
   8:         .IWant("to be able to close the application")
   9:         .SoThat("I can do other stuff");
  10:
  11:     story
  12:         .WithScenario("user clicks the close button")
  13:         .Given("the application is running")
  14:         .When("the close button is pressed")
  15:         .Then("the application should terminate");
  16: }

If we have more complicated scenarios, we can also add additional steps to each fragment (the Given/When/Then blocks) like so:

   1: .When("the F1 key is held down")
   2: .And("the mouse hovers over element X")
   3: .Then(...);

This is preferable to lumping disparate steps into the same fragment; for the best readability, we really want to keep one note : one set of code. When we have our spec in order, we can flesh out each fragment by adding delegates to each one, like so:

   1: Given("the application is running",
   2:     delegate
   3:     {
   4:         process = StartProcess(ExecutablePath);
   5:         windowElement = GetApplicationWindow(ApplicationName);
   6:     })
   7: .When(...)
   8: .Then(...)

When we run this test, we will get a report similar to the following:

   1: Theme: Main screen interaction
   2:
   3:     Story: Close application
   4:         As a User
   5:         I want to be able to close the application
   6:         So that I can do other stuff
   7:
   8:         Scenario: user clicks the close button
   9:             Given the application is running
  10:             When the close button is pressed
  11:             Then the application should terminate

Whereas, should a step fail:

   1: Then the application should terminate - FAILED

I don’t know about you, but I find a test report like that to be much more presentable, especially if I need to go over it with a non-developer. ‘sides, the tests are quite readable, so even other developers should have it easier.

kick it on DotNetKicks.com

2 Replies to “Behaviour driven development on user interfaces with Automation peer”

  1. Thanks Karl for a great article with some interesting ideas.

    What are the steps needed to run the test – I have built the projects.

    David Roh

  2. Thank David, glad you found the article interesting. To run the tests, you will need a test runner. You can either download testdriven.net and run the tests from visual studio by right clicking the test class in the editor or solution explorer and selecting the run tests option from the context menu, or you can use an external test runner like Gallio.

    You can find testdriven.net at http://www.testdriven.net/ and Gallio at http://www.gallio.org/ – I’d install both anyway. Gallio is useful for the html reports it generates when tests are run in VS, and TD.Net makes it very easy to run the tests.

    I noticed that the test reports for NBehave don’t always come up. If this is the case for you, try swapping out the MBUnit assemblies included in the download with the assemblies for v2.4.2.130 – that seems to work consistently.

    HTH

Comments are closed.