BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles TDD with Selenium and Castle

TDD with Selenium and Castle

Introduction

Test Driven Development samples are mostly based on very simple unit tests. The challenge is often how to use TDD in a larger application. It will be demonstrated in this tutorial how to build a web application using test first principles using Selenium and Castle.

Preparation

Let's say that a developer needs to write a method using 'test first' for the following feature for an application
manage users (add new, delete, edit user details, list all)

In this test case, each user will have a Full Name, a Username , a Password and an Email, all of which are mandatory.

Basic TDD Steps

Following typical TDD steps:

  1. Write the test
  2. Make it fail
  3. Write the code to make the test succeed
  4. Refactor
  5. Repeat and start at Step

The First Test

The first test that should be written is a test to add a new user. Test Driven Development is a design technique rather than a testing technique, because when writing the test, we will define how the code/page will work, therefore design.

In order to be able to add a new user a simple form like this is sufficient:

For the functional test the developer needs to open up the add page (prepare stage), fill the fields and save (action stage) and to verify if the user was actually saved (verification stage of the project). In order to do that, the developer needs to update the page and add a new list with the users on the left side, where later the code can verify the user exists after clicking save.

Selenium comes into Action

For a task like this developers need a tool that can actually perform this action on their behalf. Selenium conveniently does this in a browser and this is an excellent open source tool, such that it can be modified to your own needs I necessary. Selenium provides web based functional tests, and also allows the tests to be written as simple html tests, providing an interpreter that runs those actions for the developer:

The great news for developers who want to integrate their tests with a continuous integration tool is that they can write the tests in their own preferred language like C#, Java, VB.NET, Ruby, and Python using an extension of Selenium called Selenium RC.

Using Selenium RC, the .NET version of the test would look like:

Step 2, Making the Initial Test Fail

At this stage the developer hasn't written any code, so the test should fail. First start the Selenium RC server (a small Java server that handles the Selenium commands and transmits them to the browser):

>java -jar selenium-server.jar

Running the test fails as expected:

This is a good sign as this means the test fails when it should. Otherwise the test wouldn't really test anything and would be worthless.

Step 3, Write the Code

On step 3 in the TDD steps, the developer needs to write the code. This means when run against the tests, the code should not fail. Next create the user controller, then view and run the test:

Next create an empty add.vm and rerun the test:

Selenium.SeleniumException: ERROR: Element link=Add new user not found

at Selenium.HttpCommandProcessor.DoCommand(String command, String[] args)
at Selenium.DefaultSelenium.Click(String locator)
at MRProjectTest.Functionals.Selenium.ManageUsersTests.TestAddNewUser() in
ManageUsersTests.cs:line 34

Since the error says that it cannot find the elements on the page, we add them in add.vm:

Retest:

.... an error once again since it submits the content on the form to create.aspx and clicking the button on the page has not been implemented yet.

Next add the code to save the data:

Now wait because there does not exist a user class, the list action or the database.

TDD-ing the Layers under the Presentation Layer

In order to build the code the developer needs to build it using 'test first'. Although in some cases this isn't really necessary since ActiveRecord is already quite well tested and it is also covered by the functional test. It is still demonstrated to show how far more complicated situations should be handled.

Below is the test, which is not a functional test but is an integration test, a unit test that also uses the database):

Test if it fails. In fact it does even not compile so the first thing would be to create a User class, with empty methods to force the code to compile:

Now, run the test:

Castle.ActiveRecord.Framework.ActiveRecordException: An ActiveRecord class (UserManagement.Model.User) was used but the framework seems not properly initialized. Did you forget about ActiveRecordStarter.Initialize() ?

at Castle.ActiveRecord.ActiveRecordBase.EnsureInitialized(Type type)
at Castle.ActiveRecord.ActiveRecordBase.Save(Object instance)
at Castle.ActiveRecord.ActiveRecordBase.Save()
at MRProjectTest.Database.UsersDataAccessTests.TestSaveNewUser()
in UserDataAccessTest.cs:line 23

The error indicates the User class is not initialized in ActiveRecord, for that instance adapt the test thus:

Add the appropriate attributes for ActiveRecord and the constructors and by reruning the test. Now a corresponding database table is missing, but that is quickly remedied by adding the following line in the test:

ActiveRecordStarter.CreateSchema();//create the database schema

After running the test the database table was created, but there is still a problem:

System.NotImplementedException: todo

at UserManagement.Model.User.Find(Int64 id) in User.cs:line 72
at MRProjectTest.Database.UsersDataAccessTests.TestSaveNewUser() in
UserDataAccessTest.cs:line 41

Finish implementing the Find method in the User class:

 public static User Find(long id)
{
return (User) FindByPrimaryKey(typeof(User),id,false)
}

Finally a database test that works!

Top-Down TDD Approach

Usually tests like this one aren't really required, for the two reasons mentioned earlier but it was also done so that the flow is understood in a test first vertical development environment for a n-tier application.

Back to the Functional Test

Now that the User class exists and the database access works, it is time to continue work on the presentation layer.

Implement the list action and view:

 public void List()
{
PropertyBag["users"] = User.FindAll();
}

Create a list.vm:

For the view a GridComponent could have been used. Now running the test the developer should see a working UI Test for the first time.

Edit functionality

Next there is a need to add the edit user functionality to the site. The functionality will flow like this: on the list of users page each user will have an edit link, which once clicked upon will transfer the user to editing where the class can modify the user details. When the form is saved the user is sent back to the list. Now write the test:

A User is added to the database so when the list page is opened, there is something to edit. There is still a problem. If the test is run twice the user will be inserted twice in the database. In order to avoid this potential error do the following:

Running all the tests, the edit test now fails:

Selenium.SeleniumException: ERROR: Element link=Edit not found

at Selenium.HttpCommandProcessor.DoCommand(String command, String[] args)
at Selenium.DefaultSelenium.Click(String locator)
at MRProjectTest.Functionals.Selenium.ManageUsersTests.TestEditUser() in
ManageUsersTests.cs:line 28

To rectify this problem add the Edit link in the list.vm:

Edit the action in the controller:

 public void Edit(long id)
{
PropertyBag["user"] = User.Find(id);
}

Now edit the view for this action: edit.vm

Since the value will be saved in the update action we'll also have:

 public void Update([DataBind("user")] User user)
{
user.Update();
RedirectToAction("list");
}

Success!!

Begin Refactoring

There are a few areas of opportunity for refactoring that can be made. First, the TestAddNew and TestEdit methods are almost similar and:

ALSO:

Running the tests, they should all still work. Now go further into the views, which have a similar problem: add.vm and edit.vm are almost identical. Separate the common part into _form.vm. Running the tests still confirms the fact that the application is passing tests:

For delete, use the same test first principles. For the data validations or any other functionality the user will be able to use, add new tests, then finish by adding the code to make them pass those tests.

Conclusion

This is an example of designing an application with TDD using a method called incremental architecture. In the actual architecture of the system the architect and developer need not take a month in advance for design. The architecture and design is constructed as the code is written and tested. In this manner changes are very easily made since continuous refactoring is done to the code to make it better, all provided by TDD principles and using 'test first'.

Resources

  1. Selenium - http://www.openqa.org - open source web functional testing tools
  2. Castle Project (MonoRail and ActiveRecord) - http://www.castleproject.org/ - open source lightweight ASP.NET/ADO.NET alternative

DOWNLOAD SOURCE CODE

Rate this Article

Adoption
Style

BT