The first step on the road to acceptance testing is probably the hardest: getting control over the UI from within a standard TestMethod. It means opening up the UI code to external calls, something you typically don’t want to do, so most UI frameworks are not built with this purpose in mind.
The way we chose to start our UI test framework is to open up access to the controls on the forms directly. We thought this would be a better choice than using Reflection or sending click and key events, because, as our test runs in the same thread as the UI, we do not need to add delays anywhere, nor do we need to worry about changes in layout of the form.
The way we emulate the user is by simply setting control properties like “Text” and “SelectedValue” and by using basic methods like “PerformClick()”. To be able to get at these we marked access on the controls as “internal” rather than “protected” or “private”. This means that the access to the controls is still controlled, they are not “public”, but we can now use a clever trick to get at them externally. For this to work we used the InternalsVisibleTo attribute in the AssemblyInfo.cs file to let our test project reach into the UI code:
#if DEBUG
[assembly: InternalsVisibleTo("MyCompany.MyProject.UI.Testfixtures")]
#endif
The parameter of this attribute is the name of the assembly you want to be able to access internals directly, in our case this is the test project. You can, if you want to, enter a fully qualified name including public key token to be more secure. Adding the “if DEBUG” directive, however, ensures that this potential backdoor is not exposed in the final production version in any case.
The next step is, basically to take a simple task and automate it. The UI in our project is quite straight forward. It has a login, a couple of dashboards and a lot of wizards. So before we could do anything we needed to construct a “simple” helpermethod to logon as a particular user and check the dashboard is filled correctly. Depending on your system this may already be a significant hurdle, it was in our case. But once this was done, testing the wizards became pretty easy.
The next couple of helpermethods were things like navigation (check if the Next button is available, click it, check validation of the page we left succeeded and check we are now on the correct next page of the wizard), a method to check for expected messages and validation errors, select items in lists and grids, etc…
Going along we built a little framework which helps us write easy to understand and easy to maintain tests for most of the major functionality of the UI. A typical test now looks something like this:
[TestMethod]
public void NewCustomerWizardTest()
{
// Test scenario:
// 1) Login as user with add customer rights
// 2) Open the dashboard
// 3) Open the add customer wizard
// 4) Fill in customer details
// 5) Finish wizard
// 6) Open new customer’s dashboard – should be empty
// 7) Finish test
// Create a random name to check result
string customerName = string.Format(“TestName_{0}”, Guid.NewGuid());
// Login with a user account that has appropriate rights
LogonUser(“AdminUser1”);
// Open the users dashboard
UserDashboard userDashboard = OpenUserDashboard();
// Start the new customer wizard by clicking “Add customer” button
NewCustomerWizard newCustomerWizard =
StartWizard<NewCustomerWizard>(“Add customer”);
GeneralDataPage generalDatapage =
newCustomerWizard.CurrentPage as GeneralDataPage;
Assert.IsNotNull(generalDatapage);
generalDataPage.NameTextBox.Text = customerName;
generalDatapage.FirstNameTextBox.Text = “First”;
// etc...
// Check validation succeeded and finish wizard
Assert.AreEqual(string.Empty, generalDataPage.MessageDisplay.FormErrorMessage);
// Open the customer's dashboard
CustomerDashboard customerDashboard =
FinishWizardAndOpenCustomerDashboard<CustomerDashboard>();
Assert.AreEqual(customerName, customerDasboard.CustomerName);
Assert.AreEqual(0, GridHelper.CountRows(customerDashboard.ContractGrid));
// Test finished successfully
Logoff();
}
As you have probably guessed from the example above, doing thing this way creates a lot of surplus data in the database which we would need to get rid of somehow. That may not be very easy if customer information is synced to other systems automatically. We need to untie the UI from these backend systems, but still be able to test enough of the front end system. Squaring this circle will be the topic of the next part in this series.
By Nick Verschueren, .Net Solutions Architect