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

Chapter 2. Unit Tests

2.2. Interactions in Unit Tests

To understand what should be tested by unit tests, and how, we need to take a closer look at the interactions between the test class and the SUT, and the SUT and its DOCs1.

First, some theory in the form of a diagram. Figure 2.1 shows possible interactions between an SUT and other entities.

Figure 2.1. Types of collaboration with an SUT

Two interactions are direct, and involve the SUT and its client (a test class, in this case). These two are very easy to act upon - they are directly "available" from within the test code. Two other interactions are indirect: they involve the SUT and DOCs. In this case, the client (a test class) has no way of directly controlling the interactions.

Another possible classification divides up interactions into inputs (the SUT receiving some message) and outputs (the SUT sending a message). When testing, we will use direct and indirect inputs to set the SUT in a required state and to invoke its methods. The direct and indirect outputs of the SUT are expressions of the SUT’s behaviour; this means we shall use them to verify whether the SUT is working properly.

Table 2.1 summarizes the types of possible collaboration between an SUT and DOCs. The first column

– "type of interaction" – describes the type of collaboration from the SUT’s point of view. A test class acts as a client (someone who uses the SUT); hence its appearance in the "involved parties" column.

Table 2.1. Types of collaboration with an SUT within test code

 

type of

involved parties

description

 

interaction

 

 

 

 

 

 

 

 

direct input

 

Calls to the methods of the SUT’s API.

 

 

 

Test class & SUT

 

 

direct output

Values returned by the SUT to the test class after

 

 

 

 

calling some SUT method.

 

 

 

 

 

 

indirect output

 

Arguments passed by the SUT to a method of one

 

 

 

 

of its collaborators.

 

 

 

SUT & DOCs

 

 

indirect input

Value returned (or an exception thrown) to the

 

 

 

 

SUT by collaborators, after it called some of their

 

 

 

 

methods

 

 

 

 

 

 

 

 

 

 

1An SUT is a thing being tested; DOCs are its collaborators. Both terms are introduced in Section 1.2.

14

Chapter 2. Unit Tests

A code example will make all of this clear. Let’s imagine some financial service (FinancialService class) which, based on the last client payment and its type (whatever that would be), calculates some "bonus".

Listing 2.1. Example class to present various types of interaction in unit tests

public class FinancialService {

.... // definition of fields and other methods omitted

public BigDecimal calculateBonus(long clientId, BigDecimal payment) { Short clientType = clientDAO.getClientType(clientId);

BigDecimal bonus = calculator.calculateBonus(clientType, payment); clientDAO.saveBonusHistory(clientId, bonus);

return bonus;

}

}

As you can see the SUT’s calculateBonus() method takes two parameters (clientId and payment) and interacts with two collaborators (clientDAO and calculator). In order to test the calculateBonus() method thoroughly, we need to control both the input parameters (direct inputs) and the messages returned from its collaborators (indirect inputs). Then we will be able to see if returned value (direct output) is correct.

Table 2.2 summarizes the types of interaction that happen within the calculateBonus() method, and that are important from the test point of view.

Table 2.2. Collaborations within the calculateBonus() method

type of

involved parties

description

interaction

 

 

 

 

 

direct input

 

Direct call of the calculateBonus() method of the SUT with

 

Test class & SUT

clientId and payment arguments

 

 

direct output

bonus value returned by the SUT to the test class after it

 

 

 

called the calculateBonus() method

 

 

 

indirect output

 

clientId and bonus passed by the SUT to the

 

 

saveBonusHistory() method of clientDAO

 

SUT & DOCs

clientType and payment passed by the SUT to the

 

calculateBonus() method of calculator

 

 

 

 

 

indirect input

 

clientType returned by clientDAO, and bonus returned by

 

 

calculator to the SUT

 

 

 

2.2.1. State vs. Interaction Testing

Let us now recall the simple abstraction of an OO system shown in Figure 1.1. It shows how two kinds of classes - workers and managers - cooperate together in order to fulfill a request issued by a client. The book describes unit testing of both kind of classes. First we shall dive into the world of workers, because we want to make sure that the computations they do, and the values they return, are correct. This part of unit testing – called state testing – is really simple, and has been fully recognized for many years. This kind of test uses direct inputs and outputs. We will discuss state testing in Chapter 3, Unit Tests with no Collaborators.

15

Chapter 2. Unit Tests

Then we will move into the more demanding topics connected with interactions testing. We will concentrate on the work of managers, and we will concentrate on how messages are passed between collaborators. This is a far trickier and less intuitive kind of testing. Every so often, new ideas and tools emerge, and there are still lively discussions going on about how to properly test interactions. What is really scary is that interaction tests can sometimes do more harm than good, so we will concentrate not only on how but also on whether questions. This kind of test concentrates on indirect outputs. We will discuss interactions testing in Chapter 5, Mocks, Stubs, Test Spies.

Testing of direct outputs is also called "state verification", while testing of indirect outputs is called "behaviour verification" (see [fowler2007]).

2.2.2. Why Worry about Indirect Interactions?

An object-oriented zealot could, at this point, start yelling at me: "Ever heard of encapsulation and information hiding? So why on earth should we worry about what methods were called by the SUT on its collaborators? Why not leave it as an implementation detail of the SUT? If this is a private part of the SUT implementation, then we should not touch it at all."

This sounds reasonable, doesn’t it? If only we could test our classes thoroughly, just using their API! Unfortunately, this is not possible.

Consider a simple example of retrieving objects from a cache.

Let us remember what the general idea of a cache is. There are two storage locations, the "real one", with vast capacity and average access time, and the "cache", which has much smaller capacity but much faster access time2. Let us now define a few requirements for a system with a cache. This will not be a fully-fledged cache mechanism, but will be sufficient to illustrate the problem we encounter.

When asked for an object with key X, our system with its cache should act according to the following simple rules:

1.if the object with key X is not in any storage location, the system will return null,

2.if the object with key X exists in any storage location, it will be returned,

a.if it exists in the cache storage, it will be returned from this storage location,

b.the main storage location will be searched only if the object with key X does not exist in the cache storage3.

The point is, of course, to have a smart caching strategy that will increase the cache hit ratio4 – but this is not really relevant to our discussion. What we are concerned with are the outputs (returned values) and the interactions between the SUT and its collaborators.

If you consider the requirements listed above, you will notice that with state testing we can only test two of them - 1 and 2. This is because state testing respects objects’ privacy. It does not allow one to

2In fact, it would be more correct to say that access to one storage area is cheaper than to the other one. Usually, the unit of cost is time-relative, so we will make such a simplification here.

3Requirements 2a and 2b could also be expressed as follows: "first search in the cache storage, then in the main storage".

4Which basically means that most of the items will be in the cache when requested, and the number of queries to the real storage will be minimized.

16

Chapter 2. Unit Tests

see what the object is doing internally – something which, in our case, means that it cannot verify from which storage area the requested object has been retrieved. Thus, requirements 2a and 2b cannot be verified using state testing.

This is illustrated in the picture below. Our SUT, which consists of two storage locations (a fast cache storage and a slower real storage), is accessible via a single get() method. The client, who sends requests to the SUT, knows nothing about its internal complexity.

Figure 2.2. Is this storage working correctly or not?

Ideally, when a request comes first the cache storage is searched and then, in case the cache storage does not have an entry with the given key (X in this example), the main storage is searched. However, if the SUT is not implemented correctly then it can first look into the main storage without checking the faster storage first. The client who waits for an object with the given key can not distinguish between these two situations. All he knows is that he requested an object with key X and that he got it.

In order to really verify whether our system is working as it is supposed to or not, interaction testing must by applied. The order of calls to collaborators – cache and real storage – must be checked. Without this, we cannot say whether the system is working or not.

This simple example proves that verification of the observable behaviour of the SUT (its direct outputs) is not enough. Similar issues arise when testing managers (see Section 1.1), which coordinate the efforts of others. As mentioned previously, such coordinating classes are quite popular in OO systems. This is why we will be spending a great deal of time discussing techniques, tools and issues related to indirect outputs testing.

But to begin with let’s concentrate on the simpler case. In the next section we will learn how to test simple objects that do not have any collaborators.

17

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