Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Tomek Kaczanowski - Practical Unit Testing with JUnit and Mockito - 2013.pdf
Скачиваний:
228
Добавлен:
07.03.2016
Размер:
6.59 Mб
Скачать

Chapter 9. Organization Of Tests

9.6. Creating Complex Objects

Up till now, all the objects we have created for testing purposes have been dead simple. That may have been handy for demonstrating various different issues involved in unit testing, but it has not been at all realistic. In this section we take a look at issues relating to the creation of complex objects for test purposes.

Writing tests which use rich domain objects can turn out to be tedious work. To test their different functionalities you need to set them with different sets of properties. Unfortunately this results in long and obscure tests, which are hard to understand or maintain. The following features are symptomatic of a weak approach to test fixture creation:

a lot of test fixture code in every test method,

duplicated code - usually because multiple tests require similar objects,

test abstraction polluted by detailed objects creation parts.

A common approach to improving this situation is to create some private methods that will be responsible for the creation of domain objects. Unfortunately, often this does not really cure the illness, but replaces it with another one - an entangled web of small methods calling one another. This might be a real nightmare to understand, debug and maintain.

In this section we will learn about two approaches to dealing with domain objects creation. They are not guaranteed to make our tests perfect in this respect, but their use should at least limit the harm done by test fixture setup code.

In my "career" as a code reviewer, I have witnessed many tests which have been completely unreadable, owing to the chaotic way in which the domain objects had been created.

For the purposes of our discussion we will be using a simple Employee class. Let us assume that this class is immutable6 - something which, in general, is a good thing. There is no point in showing the code of Employee. For the sake of the ensuing discussion it will suffice to say that it has numerous fields (some being primitives and some objects of the Address, Phone and Position types), and that all its fields are initialized via a constructor (no setters).

This time we are not really interested in tests (assertion) as such, but only in that part of them which is responsible for objects creation. An example of Employee objects creation within test code is shown below.

6See http://en.wikipedia.org/wiki/Immutable_object

204

Chapter 9. Organization Of Tests

Listing 9.11. Utility method to create employees

public class EmployeeTest {

private static Phone MOBILE_PHONE = new Phone("123-456-789"); private static Phone STATIONARY_PHONE = new Phone("123-456-789"); private static Address HOME_ADDRESS = new Address("any street");

private Employee createEmployee(String name, String surname, Position ceo) {

return new Employee(name, surname, ceo, HOME_ADDRESS, MOBILE_PHONE, STATIONARY_PHONE);

}

Some properties of the Employee class, not important to the test cases, are reused between tests.

Listing 9.12. Creation of employess

@Test

public void ceoCanDoEverything() { Calendar cal = Calendar.getInstance(); cal.set(2010, 3, 1);

Date startCeo = cal.getTime(); cal.add(Calendar.DATE, 1); Date endCeo = cal.getTime();

Position ceo = new Position("ceo", startCeo, endCeo);

Employee ceoEmpl = createEmployee("ceoName", "ceoSurname", ceo); // some methods execution and assertions here

}

@Test

public void pmCanDoALot() {

Calendar cal = Calendar.getInstance(); cal.set(2008, 7, 12);

Date startPm = cal.getTime(); cal.add(Calendar.YEAR, 3); Date endPm = cal.getTime();

Position ceo = new Position("pm", startPm, endPm);

Employee pmEmpl = createEmployee("pmName", "pmSurname", ceo); // some methods execution and assertions here

}

}

Both test methods contain similar, yet slightly different, code, responsible for the creation of an employee.

Execution of the createEmployee() method.

There is nothing special about the code shown above. It is the kind of code that all of us have created more than once. It is not especially nice or readable, but it serves its purpose. A private method createEmployee() facilitates, at least to some extent, the creation of similar objects.

The test code shown in Listing 9.12 is aware of the details of the Employee class. There is no reasonable abstraction which would make reading the code easier. All we really want to do in the test methods is create a project manager and a CEO. But it is not possible to do this "just like that" - we need to go deep into some properties of the Employee class to achieve this simple thing. This lowers the readability of the test code.

Looking at the code in Listing 9.12, we can predict its future evolution. Probably some more static values will be used (e.g. are start and end dates important? - if not they will end up as static values). During the

205

Chapter 9. Organization Of Tests

implementation of more sophisticated test scenarios, it will transpire that some properties of employees, e.g. mobile phone, will become important. Then, new private utility methods will be created, to make it possible to create employees with certain fields set with default or custom values. Private methods will start to call one another (code reuse is good, is it not?) and the code will grow. Very soon, working with it will not be a pleasure anymore.

The immutability of objects, which in general is a good thing, makes creating multiple objects of the same kind rather cumbersome. You cannot reuse an object created in one test method (or in some "setup" section) and change some of its properties using setters. If the Employee class had not been immutable, then it would have been easier to reuse it across tests. In the ensuing sections we will learn how to make immutability less difficult to work with.

9.6.1. Mummy Knows Best

It is time to introduce an Object Mother pattern. Basically, this is a Factory pattern7, whose purpose is test fixture creation. Each Object Mother method creates a single aggregate. Object Mother centralizes objects creation and makes tests more readable by providing intention-revealing methods. We can think of Object Mother as a more object-oriented alternative to the private utility methods presented in the previous section. If we were to move the code from both of the test methods shown in Listing 9.12 to a separate class, we could then rewrite the test code, so that it would then look as shown in Listing 9.13.

Listing 9.13. Object Mother pattern

public class EmployeeObjectMotherTest {

@Test

public void ceoCanDoEverything() {

Employee empl = ObjectMotherEmployee.ceoEmployee(); // some methods execution and assertions here

}

@Test

public void pmCanDoALot() {

Employee empl = ObjectMotherEmployee.pmEmployee(); // some methods execution and assertions here

}

}

The above code is much clearer, and does not delve into the implementation details of the Employee class. All this is enclosed within the ObjectMotherEmployee class. This kind of separation of concerns between test code and objects creation code is definitely a good thing.

In some implementations, Object Mother goes beyond the standard factory pattern by providing methods which facilitate changing objects during tests. For example, an addAddress() method can be used to simplify the setting of a new employee’s address. It can even work with an immutable Employee class: for example, by copying data from one Employee object into another, via a constructor.

On the downside, let us note that the ObjectMotherEmployee class might soon become quite bloated. Eventually, we may end up with numerous Object Mothers (calling one another), or with an Object Mother with plenty of methods (making it a God Object8) - hard both to understand and to maintain.

7See http://www.oodesign.com/factory-pattern.html

8See http://en.wikipedia.org/wiki/God_object

206

Chapter 9. Organization Of Tests

9.6.2. Test Data Builder

Another approach we can adopt is to employ a different creational pattern. This one - called Test Data Builder - was also created with test code in mind. It requires some additional coding, but in return it offers a kind of internal DSL - one specializing in objects creation. This opens up some new possibilities.

Let us have a look at the code shown in Listing 9.14. It presents a new class - EmployeeBuilder - which will be used to construct very different objects of the Employee type. Some of its fields and methods have been omitted for the sake of brevity.

Listing 9.14. Test Data Builder pattern

public class EmployeeBuilder {

private String firstname; private String lastname; private Address address;

... some more fields here

private Position position;

public EmployeeBuilder withFirstname(String firstname) { this.firstname = firstname;

return this;

}

public EmployeeBuilder withLastname(String lastname) { this.lastname = lastname;

return this;

}

public EmployeeBuilder withAddress(Address address) { this.address = address;

return this;

}

... some more similar methods here

public EmployeeBuilder withPosition(Position position) { this.position = position;

return this;

}

public Employee build() {

return new Employee(firstname, lastname, position, address, mobile, stationary);

}

}

Each field of the Employee class is represented by a setter method which returns an instance of EmployeeBuilder. This allows us to chain the methods in any order.

The build() method calls a constructor of the Employee class and returns the result of the creation.

In addition to EmployeeBuilder, we also need builders for all of the other domain classes that will be used (by composition) by the Employee class. Such builders would be very similar to the EmployeeBuilder class we were discussing earlier. Listing 9.15 provides an example of another builder.

207

Chapter 9. Organization Of Tests

Listing 9.15. Test Data Builder used to create Position class

public class PositionBuilder {

private String title; private Date from; private Date to;

public PositionBuilder withTitle(String title) { this.title = title;

return this;

}

public PositionBuilder start(int year, int month, int day) { Calendar cal = Calendar.getInstance();

cal.set(year, month, day); this.from = cal.getTime(); return this;

}

public PositionBuilder end(int year, int month, int day) { Calendar cal = Calendar.getInstance();

cal.set(year, month, day); this.to = cal.getTime(); return this;

}

public Position build() {

return new Position(title, from, to);

}

}

What is interesting in Listing 9.15 are its start() and end() methods. They take integers (year, month, day) as parameters, which, as we shall see in a minute, make them easier to use. By using primitives in the API of this builder, we free up its clients from being involved in the cumbersome process of creating Date objects.

Let us now take a look at how two such builders might be used in tests. This is shown in Listing 9.16.

208

Chapter 9. Organization Of Tests

Listing 9.16. Test Data Builder pattern used in test code

public class EmployeeTestDataBuilderTest {

private EmployeeBuilder anEmployee() { return new EmployeeBuilder()

.withFirstname("John").withLastname("Doe")

.withMobile(

new PhoneBuilder().withNumber("123-456-789").build())

.withStationary(

new PhoneBuilder().withNumber("456-789-012").build())

.withAddress(

new AddressBuilder().withStreet("Some Street").build());

}

@Test

public void pmCanDoALot() {

Employee pmEmpl = anEmployee()

.withPosition(

new PositionBuilder().withTitle("PM")

.start(2010, 1, 1).end(2011, 7, 3).build())

.build();

// some methods execution and assertions here

}

@Test

public void ceoCanDoEverything() { Employee ceoEmpl = anEmployee()

.withPosition(

new PositionBuilder().withTitle("CEO")

.start(2011, 1, 1).end(2011, 5, 5).build())

.build();

// some methods execution and assertions here

}

}

Here we have a utility method which creates a "typical" employee, that will then be modified further. Please note, this method returns an EmployeeBuilder. Another option would be to move this method to the EmployeeBuilder class (and also make it static).

When creating objects, the anEmployee() utility method is used. This allows us to express things like "CEO is an employee but with such and such properties", etc.

If objects of other types are required, their builders are created and called.

The use made here of the start() and end() methods of PositionBuilder is very simple indeed. There is no need to create objects of the Date type.

I remember when I first saw this pattern in action: it felt very awkward to me. Back then, I found this kind of code hard to read. But that was only a first impression. After some time spent studying this pattern, I discovered that it confers several benefits:

In contrast to private methods with multiple parameters, in the case of Test Data Builders the meaning of each value is very clear. There is no confusion about what the name of a person or street, the title of a song, etc., is. Every value can be easily recognized by the method name it is passed to.

The test code can be read without problems (once you get used to it). It is clear what the properties of each created object are.

Even though the Employee objects are immutable, EmployeeBuilder objects are not. This means we can reuse and modify them.

209

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]