As the loyal followers of my blog know, I'm a big test driven development (TDD) advocate. I try and do all new development using TDD. Sometimes I fall back into my old ways but a couple ugly classes, and a few bugs usually keep me in line. The one area where I've been a little lax in my automated unit testing is in regards to the UI of my applications. As expected I often see bugs cropping up within the UI of the applications I work on. Since there aren't any unit tests to validate the UI I don't know about them until our QA analyst uncovers them, or even worse the customer. I've recently been putting some thought into the best method for testing UI's, specifically of the web variety. My current thoughts follow....
Often times when writing web based applications we wire up logic for persisting data entered into a web form within the Click event handler for the save button on the page.
protected void saveButton_Click(object sender, EventArgs e) {
if(Page.IsValid)
// load values entered into web form into business object
Customer c = new Customer();
c.FirstName = firstName.Text;
c.LastName = lastName.Text;
....
new CustomerRepository.SaveCustomer(c);
}
}
The problem with this approach is it's difficult to test the logic within the save button click event. One of the methods I've thought about using to ease the testing of the logic involves creating an interface for the "view" that specifies all the data that is entered within the web form.
public interface ICustomerView {
string FirstName { get; set; }
string LastName { get; set; }
}
The second step, after the view is defined is to create a controller that loads, and reads the view.
public class CustomerController {
public void LoadView(ICustomerView view, Customer customer) {
view.FirstName = customer.FirstName;
view.LastName = customer.LastName;
// ... others
}
public void ReadView(ICustomerView view, Customer customer) {
customer.FirstName = view.FirstName;
customer.LastName = view.LastName;
// ... others
}
}
Now we have an interface as well as a controller that we can test using our usually unit testing methods. Ideally we'd also write some tests to ensure that the web form that is implementing the ICustomerView interface properly loads and reads the controls that are on the form, however, with the current set of tools we have available I don't know of a way to handle that. We can use NUnitAsp to test that the controls exist and that we can read the values, however, we can't do this through our controller class. We also aren't able to ensure the base page class implements the necessary interface (ICustomerView).
Our web form utilizing the ICustomerView and Controller classes is below:
public
class CustomerEdit : System.Web.UI.Page, ICustomerView {
protected TextBox firstName;
protected TextBox lastName;
protected Button saveButton;
CustomerController controller = new CustomerController();
CustomerRepository repository = new CustomerRepository();
Customer customer = null;
public int CustomerID {
get { // read customer id from querystring, etc. }
} #region
ICustomerView Members
public string FirstName {
get { return firstName.Text; }
set { firstName.Text = value; }
}
public string LastName {
get { return lastName.Text; }
set { lastName.Text = value; }
}
#endregion private void Page_Load(object sender, System.EventArgs e) {
if (!Page.IsPostBack) {
customer = repository.GetCustomer(this.CustomerID);
controller.LoadView(this, c);
}
}
private void saveButton_Click(object sender, EventArgs e) {
if (Page.IsValid) {
customer = new Customer();
controller.ReadView(this, customer);
repository.SaveCustomer(customer);
}
}
}
The final step in the process is to verify that the view actually works. To test this NUnitAsp can be used to set the values of the controls on the web form, and then "clicking" the save button.
[TestFixture]
public class CustomerViewTest : WebFormTestCase {
public CustomerViewTest() {}
TextBoxTester firstName;
TextBoxTester lastName;
ButtonTester saveButton;
protected override void SetUp() {
firstName = new TextBoxTester("firstName", CurrentWebForm);
lastName = new TextBoxTester("lastName", CurrentWebForm);
saveButton = new ButtonTester("saveButton", CurrentWebForm);
Browser.GetPage(http://localhost/UITesting.Web/WebForm1.aspx);
}
[Test]
public void ControlsAreVisible() {
AssertVisibility(firstName, true);
AssertVisibility(lastName, true);
AssertVisibility(saveButton, true);
}
[Test]
public void PressingSaveButtonWillSaveCustomer() {
firstName.Text = "Steve";
lastName.Text = "Eichert";
saveButton.Click();
CustomerCollection customers = new CustomerRepository().FindAll();
AssertEquals(1, customers.Count);
Customer c = customers[0];
AssertEquals(firstName.Text, c.FirstName);
AssertEquals(lastName.Text, c.LastName);
}
}
Note: I couldn't actually get my tests to run within NUnit so they might not work. I keep getting a FileNotFoundException when trying which was a bit of a bummer. Does anyone have NUnitAsp running on NUnit 2.2?
What strategy do you use to run automated unit tests for your UI?