b-log – betriebsraum weblog

Software Development, Human-Computer Interaction, Projects…

Better Tests with Test Data Builders

February 8, 2010

When developing applications, often you need to set up some dummy data to test things without using real backend services. One particularly useful technique is described by Freeman and Pryce in their book »Growing Object-Oriented Software, Guided by Tests«: Using the builder pattern to construct complex test data and improve code readability. Let’s say you need a bunch of dummy (fake/mock/stub – whatever you call them) user objects and you already have a user object with a nested address object (say):

public class User {
 
    public var id:int;
    public var username:String;
    public var email:String;
    public var address:Address;
 
}
 
public class Address {
 
    public var street:String;
    public var streetNumber:String;
    public var zip:String;
    public var city:String;
 
}

You could create a mock service that implements the same interface as your real service. In your service interface, you then define a function getUsers() that returns an array of users (for simplicity let’s assume it is a synchronous operation). Still nothing exciting so far:

public interface UserService {
 
    function getUsers():Array;
 
}
 
public class MockUserService implements UserService {
 
    public function getUsers():Array {
        // return mock users
    }
 
}

Now, inside the getUsers() function you can use user builders to create your dummy objects. A simplified user builder could look like this:

public class UserBuilder {
 
    private var _id:int = 1;
    private var _address:AddressBuilder = new AddressBuilder();
 
    public function UserBuilder() {
 
    }
 
    public static function aUser():UserBuilder {
        return new UserBuilder();
    }
 
    public function withID(id:int):UserBuilder {
        _id = id;
        return this;
    }
 
    public function withAddress(address:AddressBuilder):UserBuilder {
        _address = address;
        return this;
    }
 
    public function build():User {
        var user:User = new User();
        user.id = _id;
        user.username = "user" + _id;
        user.email = user.username + "@host.com";
        user.address = _address.build();
        return user;
    }
 
}
 
public class AddressBuilder {
 
    private var _street:String = "Teststreet";
    private var _streetNumber:String = "1A";
    private var _zip:String = "12345";
    private var _city:String = "Testcity";
 
    public function AddressBuilder () {
 
    }
 
    public static function anAddress():AddressBuilder {
        return new AddressBuilder();
    }
 
    public function build():Address {
        var address:Address = new Address();
        address.street = _street;
        address.streetNumber = _streetNumber;
        address.zip = _zip;
        address.city = _city;
        return address;
    }
 
}

..and the implementation of getUsers() in MockUserService could look like this:

public class MockUserService implements UserService {
 
    public function getUsers():Array {
        var mockUsers:Array = new Array();
        for (var i:int = 1; i <= 100; i++) {
            mockUsers.push(UserBuilder.aUser().withID(i).build());
        }
        return mockUsers;
    }
 
}

Whenever you need a user you simple call new UserBuilder().build() or UserBuilder.aUser(). All knowledge to contruct a new user is encapsulated in the user builder and values are initialized to reasonable defaults. Using the fluent interface/method chaining, you can change the default values and make your code more readable by clearly expressing its purpose (in contrast to constructor parameters, for example). Also, you can chain different builders together (see the withAddress() method in UserBuilder) and further improve code clarity by using additional factory methods that return a new builder. Then, you can write lines such as:

aUser().withAddress(anAddress().withCity("Foo")).build();

You *could* use a mocking framework to create test data but in a recent article Uncle Bob shows that it might not always be the best idea…and you also might not want to call every test class »mock«…

Happy FakeMockStubSpyDummyTestDoubling!

Filed under: Software Development

3 Responses to “Better Tests with Test Data Builders”

  1. David Buhler says:

    I had completed a project in ColdFusion a few years ago that created highly random, highly readable data for testing.

    http://code.google.com/p/coldfusionrandomdatagenerator/

    It’s a project I’ve been meaning to port over to Actionscript for quite some time, but have been busy on another project (as is always the case).

    If you find yourself needing a starting point to create AS functions that return random data that looks real enough to cross-reference and discuss with clients, it may serve your needs.

    David Buhler

  2. felix says:

    Hi David,

    thanks, interesting project for generating large test sets! (the main point of this article is to show how to encapsulate test data using OOP and constructing objects using a clean interface…)

  3. Michael says:

    Hi Felix,

    you are so cool and sophisticated. Thanks for your article. Now my tests will be much better. Your blog is a real enhancement for the community. Thanks again and go on writing those articles.

Add a comment

Follow comments to this post by subscribing to the comment feed.