Application design is one of the most important aspects of creating the application. A design serves as the pillars of the application. An incorrect design can cause delays or even worse destroy the application. In this article we will take a look at designing application using test driven development.
Introduction:
Application design is one of the most important aspects of creating the application. A design serves as the pillars of the application. An incorrect design can cause delays or even worse destroy the application. In this article we will take a look at designing application using test driven development.
Evolving the Design with Test Driven Development:
People often consider the process of test driven development as testing the application for different inputs. But test driven development is much more than inputs and results. Test driven development allows the developer to design the application and makes sure that the developer only writes the code that is required to produce the desired output.
The purpose of this article is to show you how the application design evolves with the help of test driven development.
Scenario:
There is no actual scenario to discuss. We will start by testing two classes Person and Address. A Person can have many Addresses. An Address must belong to a Person. As we progress we will find out that the user stories and features keep on growing and unit tests serves as a shield to protect us from those changes.
Person Entity:
According to the client the Person must have the following attributes.
FirstName: First name of the person
LastName: Last name of the person
MiddleName: Middle name of the person
DOB: Date of birth
DateCreated: Date and time the person entry was created
DateModified: Date and time the person entry was modified
The Person entity class as shown below:
public class Person
{
private string _firstName;
private string _lastName;
private DateTime _dob;
private int _id;
public int Id
{
get { return _id; }
internal set { _id = value; }
}
public string FirstName
{
get { return _firstName; }
set {
_firstName = value;
}
}
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
}
}
public DateTime DOB
{
get { return _dob; }
set {
_dob = value;
}
}
}
What to Test?
One common problem that developers encounter is getting started with test driven development. How should we find out what modules to test? The best way is to study what module is extensively used in the application. Once, you find the module start writing unit tests. A good code coverage tool will also reveal important information about the usage of modules in your application.
Keeping that in mind we picked up the phone and gave a call to our client to discuss the constraints on the application. The client explained us the following:
1) First name cannot be left empty
2) Last name cannot be left empty
3) Person must be at least 18 years old
This gives us a good starting point to test our application. Let’s start by making sure that the above expectations are met.
Implementing Unit Tests:
Here is our first test case that makes sure that the properties are not set if they are in invalid state.
[RowTest]
[Row("","","02/09/2000")]
public void should_not_set_person_properties_if_in_invalid_state(string firstname, string lastname,DateTime dob)
{
Person person = new Person() { FirstName= firstname, LastName = lastname, DOB = dob };
Assert.IsTrue(person.FirstName != firstname,"FirstName has been set!");
Assert.IsTrue(person.LastName != lastname,"LastName has been set!");
Assert.IsTrue(person.DOB != dob,"DOB has been set!");
}
The test “should_not_set_person_properties_if_in_invalid_state” makes sure that the person properties are only set when they are in valid state. We are using the RowTest attribute provided by the MbUnit framework. The RowTest attribute enables us to provide multiple test inputs using the Row attribute. You can learn more about the attributes and the MbUnit framework by visit www.MbUnit.com.
When you run the above test it will fail. This is because we have not made any changes to our entity class Person and the values will be set. So, we need to add constraints to our Person class properties. Let’s take a look at the constraints.
public class Person
{
private string _firstName;
private string _lastName;
private DateTime _dob;
public const int LEGAL_AGE = 18;
public string FirstName
{
get { return _firstName; }
set {
if(!String.IsNullOrEmpty(value.Trim()))
{
_firstName = value;
}
}
}
public string LastName
{
get { return _lastName; }
set
{
if (!String.IsNullOrEmpty(value.Trim()))
{
_lastName = value;
}
}
}
public DateTime DOB
{
get { return _dob; }
set {
// the person must be 18 or older!
if ((DateTime.Now.Subtract(value).Days / 365) >= LEGAL_AGE)
{
_dob = value;
}
}
}
}
In the above code we added several different constraints which will help us to keep the object in the valid state. Now, if you run the test it will pass.
Now, let’s test that the Person object is saved successfully. Take a look at the test below:
[Test]
[RollBack]
public void should_check_if_the_person_saved_successfully()
{
Person person = new Person() { FirstName = "Mohammad", LastName = "Azam", DOB= DateTime.Parse("02/09/1981") };
person.Save();
Assert.IsTrue(person.Id > 0);
Person vPerson = Person.GetById(person.Id);
Assert.AreEqual(person.FirstName, vPerson.FirstName);
Assert.AreEqual(person.LastName, vPerson.LastName);
Assert.AreNotEqual(person.DOB, vPerson.DOB);
}
If you run the above test it would fail because Person object does not expose a Save or GetById method. Let’s add those two methods:
public void Save()
{
// this means adding a new person
if (this.Id <= 0)
{
SQLDataAccess.SavePerson(this);
}
else
{
SQLDataAccess.UpdatePerson(this);
}
}
public static Person GetById(int id)
{
return SQLDataAccess.GetPersonById(id);
}
The Save method uses the the SQLDataAccess data access class to insert the Person in the database. The GetById method takes “id” as the parameters and returns the Person object matching the “id”.
If you are in the initial phases of development and don’t want to work on the data access layer then you can always mock it out. Mocking means you will fake out the data access layer because it does not exists yet. Once, the data access layer is implemented you can simply plug it in without changing any test code.
Let’s move on to the Address entity.
public class Address
{
private int _id;
private string _street;
private Person _person;
public int Id
{
get { return _id; }
internal set { _id = value; }
}
public string Street
{
get { return _street; }
set { _street = value; }
}
public Person Person
{
get { return _person; }
set { _person = value; }
}
public Address(Person person)
{
this.Person = person;
}
The Address class contains a Person object since the Address belongs to a certain Person. One important thing to note is the Address object constructor which takes a Person object.
Public Address(Person person)
{
_this.Person = person;
}
This is to make sure that the Address must have a Person object.
This process is also known as the dependency injection. There are two types of dependency injection constructor and property. In the above example we used the constructor dependency injection.
And here is a test to make sure that the Address must have a Person object or Address must belong to a Person.
[Test]
public void should_make_sure_that_an_address_must_have_a_person()
{
Address address = new Address(new Person());
Assert.IsNotNull(address.Person);
}
Simple right!
Now, let’s try to add an Address to a Person object. Here is the test.
[Test]
public void should_be_able_to_add_an_address_to_the_person()
{
Person person = new Person() { FirstName = "Mohammad", LastName = "Azam" };
Address address = new Address(person);
person.AddAddress(address);
Assert.AreEqual(1, person.Addresses.Count());
}
If you run the above test it will fail because the Person object does not have the AddAddress method and the Addresses property. So, let’s add those two things.
private IList<Address> _addresses = new List<Address>();
public IList<Address> Addresses
{
get { return new ReadOnlyCollection<Address>(_addresses); }
}
public void AddAddress(Address address)
{
address.Person = this;
_addresses.Add(address);
}
Now if you run the test it will pass.
Conclusion:
In this article we learnt how to get started with test driven development and how the TDD approach helps us to design better applications. In the next article we are going to see how our design changes when we encounter failing tests.
I hope you liked the article happy coding!
[Download]