The builder pattern is the first design pattern I intend to write about during the next weeks. Design patterns as introduced by the GoF (gang of four) are, in its essence, solutions to software design problems. I have some favorite design patterns that I tend to use many times during my projects, and the builder pattern is one of them.
Although I use the builder pattern in development code, I tend to use it more in my unit tests. To demonstrate the advantages of using the builder pattern in unit tests lets explain them with a classic example:
public
class
Person
{
public
Person(
string
firstname,
string
lastname, DateTime birthdate, string gender
)
{
this
.FirstName = firstname;
this
.LastName = lastname;
this
.BirthDate = birthdate;
this.Gender = gender;
}
public
string
FirstName {
get
;
private
set
; }
public
string
LastName {
get
;
private
set
; }
public
DateTime BirthDate {
get
;
private
set
; }
public
string Gender
{
get
;
private
set
; }
public
string
GetFullName()
{
return
this
.FirstName +
" "
+
this
.LastName;
}
}
public
class
PersonTest
{
[Test]
public
void
GetFullName()
{
// Arrange
Person p
=
new
Person(
"Andrew"
,
"Stevens"
,
new
DateTime(1980, 1, 1), "male"
);
// Act
string
fullname = e.GetFullName();
// Assert
Assert.That(fullname, Is.EqualTo(
"Andrew Stevens"
));
}
}
- Although we are only testing the GetFullName method, which only requires the FirstName and LastName to be filled, we need to pass the birth date, and gender, so we basically we are bound to the constructor.
- If we were to change the constructor signature, we would need to change all the tests where we instantiate the Person class.
The solution:
- Build a PersonBuilder class that takes care of creating a person in an expressive way, decoupling ourselves from calling the constructor directly.
Steps:
1. Create the PersonBuilder class:
public
class
PersonBuilder
{
private
string
firstname =
"first"
;
private
string
lastname =
"last"
;
private
DateTime birthdate = DateTime.Today;
private
string
gender =
"male"
;
public
Person Build()
{
return
new
Person(firstname, lastname, birthdate, gender);
}
}
2. Create helper methods set the private properties:
public
PersonBuilder
AddFirstName(
string
firstname)
{
this
.firstname = firstname;
return this;
}
public
PersonBuilder
AddLastName(
string
lastname)
{
this
.lastname = lastname;
return this;
}
Now with these methods, we can easily build our Person in a flexible and expressive way:
Person p =
new
PersonBuilder()
.AddFirstName("Andrew"
)
.AddLastName(
"Stevens"
)
.Build();
Person p =
new
PersonBuilder()
.AddFirstName("Andrew"
)
.AddLastName(
"Stevens"
);
public
class
PersonTest
{
[Test]
public
void
TestFullName()
{
// Arrange
Person
p =
new
PersonBuilder()
.AddFirstName(
"Andrew"
)
.AddLastName(
"Stevens"
);
// Act
string
fullname = p.GetFullName();
// Assert
Assert.That(fullname, Is.EqualTo(
"Andrew Stevens"
));
}
public
class
PersonBuilder
{
private
string
firstname =
"first"
;
private
string
lastname =
"last"
;
private
DateTime birthdate = DateTime.Today;
private
string
gender=
"male"
;
public
Person Build()
{
return
new
Person(firstname, lastname, birthdate, gender);
}
public
PersonBuilder AddFirstName(
string
firstname)
{
this
.firstname = firstname;
return
this
;
}
public
PersonBuilder Add
LastName(
string
lastname)
{
this
.lastname = lastname;
return
this
;
}
public
PersonBuilder AddGender
(
string
gender)
{
this
.gender = gender;
return
this
;
}
public
PersonBuilder Add
BirthDate(DateTime birthdate)
{
this
.birthdate = birthdate;
return
this
;
}
public
static
implicit
operator
Person(PersonBuilder
instance)
{
return
instance.Build();
}
}
- Expressiveness
- You can build your code in an expressive way, making your tests more readable.
- Reliability
- Your builder acts as a wrapper around the Person class in this example, and by doing that if you need to change the signature of the Person constructor you don’t need to touch your tests, but only the builder class. As your code matures, this will make a huge difference.
Hopefully, this will help you make your tests a little more readable. If you want to add something just contact me or comment.