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.
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:
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.
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”:
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:
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:
If we have more complicated scenarios, we can also add additional steps to each fragment (the Given/When/Then blocks) like so:
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:
When we run this test, we will get a report similar to the following:
Whereas, should a step fail:
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.