1. Trang chủ
  2. » Công Nghệ Thông Tin

xunit test patterns refactoring test code phần 6 ppsx

95 205 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Xunit Test Patterns Refactoring Test Code Phần 6
Trường học University of Calgary
Chuyên ngành Software Testing
Thể loại bài báo
Thành phố Calgary
Định dạng
Số trang 95
Dung lượng 812,83 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

One obvious solution is to put all tests that depend on the same starting state into the same Testcase Class page 373 and to create the SUT in the appropri-ate stappropri-ate in the set

Trang 1

public void testStatus_initial() {

// in-line setup

Airport departureAirport = new Airport("Calgary", "YYC");

Airport destinationAirport = new Airport("Toronto", "YYZ");

Flight flight = new Flight(flightNumber,

Airport departureAirport = new Airport("Calgary", "YYC");

Airport destinationAirport = new Airport("Toronto", "YYZ");

Flight flight = new Flight( flightNumber,

departureAirport,

destinationAirport);

flight.cancel(); // still part of setup

// Exercise SUT and verify outcome

We can refactor the fi xture setup logic by using an Extract Method refactoring

to remove any frequently repeated code sequences into utility methods with

Intent-Revealing Names We leave the calls to the methods in the test, however,

so that the reader can see what is being done The method calls that remain

within the test will convey the “big picture” to the test reader The utility

meth-od bmeth-odies contain the irrelevant mechanics of carrying out the intent If we need

to share the Delegated Setups with another Testcase Class, we can use either a

Pull Up Method [Fowler] refactoring to move them to a Testcase Superclass or

a Move Method [Fowler] refactoring to move them to a Test Helper class

Example: Delegated Setup

In this version of the test, we use a method that hides the fact that we need two

airports instead of creating the two airports needed by the fl ight within each Test

Method We could produce this version of the tests either through refactoring or

by writing the test in this intent-revealing style right off the bat

Delegated Setup

Trang 2

public void testGetStatus_initial() {

// setup

Flight flight = createAnonymousFlight();

// exercise SUT and verify outcome

Flight flight = createAnonymousCancelledFlight();

// exercise SUT and verify outcome

assertEquals(FlightState.CANCELLED, flight.getStatus());

// teardown

// garbage-collected

}

The simplicity of these tests was made possible by the following Creation Methods,

which hide the “necessary but irrelevant” steps from the test reader:

private int uniqueFlightNumber = 2000;

public Flight createAnonymousFlight(){

Airport departureAirport = new Airport("Calgary", "YYC");

Airport destinationAirport = new Airport("Toronto", "YYZ");

public Flight createAnonymousCancelledFlight(){

Flight flight = createAnonymousFlight();

Trang 3

Creation Method

How do we construct the Fresh Fixture?

We set up the test fi xture by calling methods that hide the mechanics of

building ready-to-use objects behind Intent-Revealing Names

Fixture setup usually involves the creation of a number of objects In many

cases, the details of those objects (i.e., the attribute values) are unimportant but

must be specifi ed to satisfy each object’s constructor method Including all of

this unnecessary complexity within the fi xture setup part of the test can lead to

Obscure Tests (page 186) and certainly doesn’t help us achieve Tests as

Docu-mentation (see page 23)!

How can a properly initialized object be created without having to clutter

the test with In-line Setup (page 408)? The answer, of course, is to encapsulate

this complexity Delegated Setup (page 411) moves the mechanics of the fi xture

setup into other methods but leaves overall control and coordination within

the test itself But what to delegate to? A Creation Method is one way we can

encapsulate the mechanics of object creation so that irrelevant details do not

distract the reader

Fixture Setup

Fixture Setup

Creation Method

Trang 4

How It Works

As we write tests, we don’t bother asking whether a desired utility function

exists; we just use it! (It helps to pretend that we have a loyal helper sitting

next to us who will quickly fi ll in the bodies of any functions we call that do

not exist as yet.) We write our tests in terms of these magic functions with

Intent-Revealing Names [SBPP], passing as parameters only those things that

will be verifi ed in the assertions or that should affect the outcome of the test

Once we’ve written the test in this very intent-revealing style, we must implement all of the magic functions that we’ve been calling The functions that create objects

are our Creation Methods; they encapsulate the complexity of object creation The

simple ones call the appropriate constructor, passing it suitable default values for

anything needed but not supplied as a parameter If any of the constructor

argu-ments are other objects, the Creation Method will fi rst create those depended-on

objects before calling the constructor

The Creation Method may be placed in all the same places where we put

Test Utility Methods (page 599) As usual, the decision is based on the expected

scope of reuse and the Creation Method’s dependencies on the API of the SUT

A related pattern is Object Mother (see Test Helper on page 643), which is a

combination of Creation Method, Test Helper, and optionally Automated

Tear-down (page 503)

When to Use It

We should use a Creation Method whenever constructing a Fresh Fixture

(page 311) requires signifi cant complexity and we value Tests as Documentation.

Another key indicator for using Creation Method is that we are building the

system in a highly incremental way and we expect the API of the system (and

especially the object constructors) to change frequently Encapsulating

knowl-edge of how to create a fi xture object is a special case of SUT API Encapsulation

(see Test Utility Method), and it helps us avoid both Fragile Tests (page 239) and

Obscure Tests.

The main drawback of a Creation Method is that it creates another API for

test automaters to learn This isn’t much of a problem for the initial test

devel-opers because they are typically involved in building this API but it can create

“one more thing” for new additions to the team to learn Even so, this API

should be pretty easy to understand because it is just a set of Factory Methods

[GOF] organized in some way

If we are using a Prebuilt Fixture (page 429), we should use Finder Methods (see Test Utility Method) to locate the prebuilt objects At the same time, we

Creation

Method

Trang 5

may still use Creation Methods to lay mutable objects that we plan to modify

on top of an Immutable Shared Fixture (see Shared Fixture on page 317).

Several variations of Creation Method are worth exploring

Variation: Parameterized Creation Method

While it is possible (and often very desirable) for Creation Methods to take no

parameters whatsoever, many tests will require some customization of the

cre-ated object A Parameterized Creation Method allows the test to pass in some

attributes to be used in the creation of the object In such a case, we should pass

only those attributes that are expected to affect (or those we want to

demon-strate do not affect) the test’s outcome; otherwise, we could be headed down the

slippery slope to Obscure Tests.

Variation: Anonymous Creation Method

An Anonymous Creation Method automatically creates a Distinct Generated

Value (see Generated Value on page 723) as the unique identifi er for the object it

is creating even though the arguments it receives may not be unique This

behav-ior is invaluable for avoiding Unrepeatable Tests (see Erratic Test on page 228)

because it ensures that every object we create is unique, even across multiple test

runs If the test cares about some attributes of the object to be created, it can

pass them as parameters of the Creation Method; this behavior turns the

Anony-mous Creation Method into a Parameterized AnonyAnony-mous Creation Method.

Variation: Parameterized Anonymous Creation Method

A Parameterized Anonymous Creation Method is a combination of several other

variations of Creation Method in that we pass in some attributes to be used in

the creation of the object but let the Creation Method create the unique

identi-fi er for it A Creation Method could also take zero parameters if the test doesn’t

care about any of the attributes

Variation: Named State Reaching Method

Some SUTs are essentially stateless, meaning we can call any method at any

time By contrast, when the SUT is state-rich and the validity or behavior of

methods is affected by the state of the SUT, it is important to test each method

from each possible starting state We could chain a bunch of such tests together

in a single Test Method (page 348), but that approach would create an Eager

Test (see Assertion Roulette on page 224) It is better to use a series of

Single-Condition Tests (see page 45) for this purpose Unfortunately, that leaves us

Creation Method

Trang 6

with the problem of how to set up the starting state in each test without a lot of

Test Code Duplication (page 213)

One obvious solution is to put all tests that depend on the same starting state

into the same Testcase Class (page 373) and to create the SUT in the

appropri-ate stappropri-ate in the setUp method using Implicit Setup (page 424) (called Testcase

Class per Fixture; see page 631) The alternative is to use Delegated Setup by

calling a Named State Reaching Method; this approach allows us to choose

some other way to organize our Testcase Classes.

Either way, the code that sets up the SUT will be easier to understand if it

is short and sweet That’s where a Named State Reaching Method comes in

handy By encapsulating the logic required to create the test objects in the

cor-rect state in a single place (whether on the Testcase Class or a Test Helper), we

reduce the amount of code we must update if we need to change how we put

the test object into that state

Variation: Attachment Method

Suppose we already have a test object and we want to modify it in some way We

fi nd ourselves performing this task in enough tests to want to code this modifi

ca-tion once and only once The soluca-tion in this case is an Attachment Method The

main difference between this variation and the original Creation Method pattern

is that we pass in the object to be modifi ed (one that was probably returned by

another Creation Method) and the object we want to set one of its attributes to;

the Attachment Method does the rest of the work for us

Implementation Notes

Most Creation Methods are created by doing an Extract Method [Fowler]

refac-toring on parts of an existing test When we write tests in an “outside-in”

man-ner, we assume that the Creation Methods already exist and fi ll in the method

bodies later In effect, we defi ne a Higher-Level Language (see page 41) for defi

n-ing our fi xtures Nevertheless, there is another, completely different way to defi ne

Creation Methods.

Variation: Reuse Test for Fixture Setup

We can set up the fi xture by calling another Test Method to do the fi xture setup

for us This assumes that we have some way of accessing the fi xture that the

other test created, either through a Registry [PEAA] object or through instance

variables of the Testcase Object (page 382)

It may be appropriate to implement a Creation Method in this way when

we already have tests that depend on other tests to set up their test fi xture but

Creation

Method

Trang 7

we want to reduce the likelihood that a change in the test execution order of

Chained Tests (page 454) will cause tests to fail Mind you, the tests will run

more slowly because each test will call all the preceding tests it depends on each

time each test is run rather than each test being run only once per test run Of

course, each test needs to call only the specifi c tests it actually depends on, not all

tests in the test suite This slowdown won’t be very noticeable if we have replaced

any slow components, such as a database, with a Fake Object (page 551)

Wrapping the Test Method in a Creation Method is a better option than

calling the Test Method directly from the client Test Method because most Test

Methods are named based on which test condition(s) they verify, not what (fi

x-ture) they leave behind The Creation Method lets us put a nice Intent-Revealing

Name between the client Test Method and the implementing Test Method It

also solves the Lonely Test (see Erratic Test) problem because the other test is

run explicitly from within the calling test rather than just assuming that it was

already run This scheme makes the test less fragile and easier to understand but

it won’t solve the Interacting Tests (see Erratic Test) problem: If the test we call

fails and leaves the test fi xture in a different state than we expected, our test will

likely fail as well, even if the functionality we are testing is still working

Motivating Example

In the following example, the testPurchase test requires a Customer to fi ll the role of

thebuyer The fi rst and last names of the buyer have no bearing on the act of

pur-chasing, but are required parameters of the Customer constructor; we do care that

theCustomer’s credit rating is good (“G”) and that he or she is currently active

public void testPurchase_firstPurchase_ICC() {

The use of constructors in tests can be problematic, especially when we are

building an application incrementally Every change to the parameters of the

constructor will force us to revisit a lot of tests or jump through hoops to keep

the constructor signatures backward compatible for the sake of the tests

Creation Method

Trang 8

Refactoring Notes

We can use an Extract Method refactoring to remove the direct call to the

construc-tor We can give the new Creation Method an appropriate Intent-Revealing Name

such as createCustomer based on the style of Creation Method we have created

Example: Anonymous Creation Method

In the following example, instead of making that direct call to the Customer

constructor, we now use the CustomerCreation Method Notice that the coupling

between the fi xture setup code and the constructor has been removed If another

parameter such as phone number is added to the Customer constructor, only the

CustomerCreation Method must be updated to provide a default value; the fi xture

setup code remains insulated from the change thanks to encapsulation

public void testPurchase_firstPurchase_ACM() {

Customer buyer = createAnonymousCustomer();

//

}

public void testPurchase_subsequentPurchase_ACM() {

Customer buyer = createAnonymousCustomer();

//

}

We call this pattern an Anonymous Creation Method because the identity of

the customer does not matter The Anonymous Creation Method might look

something like this:

public Customer createAnonymousCustomer() {

int uniqueid = getUniqueCustomerId();

return new Customer(uniqueid,

"FirstName" + uniqueid,

"LastName" + uniqueid,

"G", "ACTIVE");

}

Note the use of a Distinct Generated Value to ensure that each anonymous Customer

is slightly different to avoid accidentally creating an identical Customer

Example: Parameterized Creation Method

If we wanted to supply some of the Customer’s attributes as parameters, we could

defi ne a Parameterized Creation Method:

public void testPurchase_firstPurchase_PCM() {

Customer buyer =

createCreditworthyCustomer("FirstName", "LastName");

Creation

Method

Trang 9

//

}

public void testPurchase_subsequentPurchase_PCM() {

Customer buyer = createCreditworthyCustomer("FirstName", "LastName");

//

}

Here’s the corresponding Parameterized Creation Method defi nition:

public Customer createCreditworthyCustomer(

String firstName, String lastName) {

int uniqueid = getUniqueCustomerId();

Example: Attachment Method

Here’s an example of a test that uses an Attachment Method to associate two

customers to verify that both get the best discount either of them has earned or

negotiated:

public void testPurchase_relatedCustomerDiscount_AM() {

Customer buyer = createCreditworthyCustomer("Related", "Buyer");

Although this example is relatively simple, the call to this method is still easier to

understand than reading both the method calls of which it consists

Creation Method

Trang 10

Example: Test Reused for Fixture Setup

We can reuse other tests to set up the fi xture for our test Here is an example of

how not to do it:

private Customer buyer;

private AccountManager sut = new AccountManager();

private Account account;

public void testCustomerConstructor_SRT() {

public void testPurchase_SRT() {

testCustomerConstructor_SRT(); // Leaves in field "buyer"

account = sut.createAccountForCustomer( buyer );

assertEquals( buyer.name, account.customerName, "cust");

//

}

The problem here is twofold First, the name of the Test Method we are calling

describes what it verifi es (e.g., a name) and not what it leaves behind (i.e., a Customer

in the buyer fi eld Second, the test does not return a Customer; it leaves the Customer in

an instance variable This scheme works only because the Test Method we want

to reuse is on the same Testcase Class; if it were on an unrelated class, we would

have to do a few backfl ips to access the buyer A better way to accomplish this goal

is to encapsulate this call behind a Creation Method:

private Customer buyer;

private AccountManager sut = new AccountManager();

private Account account;

public void testCustomerConstructor_RTCM() {

account = sut.createAccountForCustomer( buyer );

assertEquals( buyer.name, account.customerName, "cust");

Trang 11

return buyer;

//

}

Notice how much more readable this test has become? We can see where the buyer

came from! This was easy to do because both Test Methods were on the same

class If they were on different classes, our Creation Method would have to create

an instance of the other Testcase Class before it could run the test Then it would

have to fi nd a way to access the buyer instance variable so that it could return it

Method

Trang 12

Implicit Setup

How do we construct the Fresh Fixture?

We build the test fi xture common to several tests in the setUp method.

To execute an automated test, we require a text fi xture that is well understood

and completely deterministic We are using a Fresh Fixture (page 311) approach

to build the Minimal Fixture (page 302) for the use of this one test

Implicit Setup is a way to reuse the fi xture setup code for all Test ods (page 348) in a Testcase Class (page 373)

Meth-How It Works

All tests in a Testcase Class create identical Fresh Fixtures by doing test fi xture

setup in a special setUp method on the Testcase Class The setUp method is called

automatically by the Test Automation Framework (page 298) before it calls each

Test Method This allows the fi xture setup code placed in the setUp method to

be reused without reusing the same instance of the test fi xture This approach is

called “implicit” setup because the calls to the fi xture setup logic are not explicit

within the Test Method, unlike with In-line Setup (page 408) and Delegated

Trang 13

When to Use It

We can use Implicit Setup when several Test Methods on the same Testcase Class

need an identical Fresh Fixture If all Test Methods need the exact same fi xture,

then the entire Minimal Fixture needed by each test can be set up in the setUp

method This form of Test Method organization is known as Testcase Class per

Fixture (page 631)

When the Test Methods need different fi xtures because we are using a Testcase

Class per Feature (page 624) or Testcase Class per Class (page 617) scheme, it

is more diffi cult to use Implicit Setup and still build a Minimal Fixture We can

use the setUp method only to set up the part of the fi xture that does not cause any

problems for the other tests A reasonable compromise is to use Implicit Setup to

set up the parts of the fi xture that are essential but irrelevant and leave the setup

of critical (and different from test to test) parts of the fi xture to the individual

Test Methods Examples of “essential but irrelevant” fi xture setup include

ini-tializing variables with “don’t care” values and iniini-tializing hidden “plumbing”

such as database connections Fixture setup logic that directly affects the state of

the SUT should be left to the individual Test Methods unless every Test Method

requires the same starting state

The obvious alternatives for creating a Fresh Fixture are In-line Setup, in

which we include all setup logic within each Test Method without factoring out

any common code, and Delegated Setup, in which we move all common fi xture

setup code into a set of Creation Methods (page 415) that we can call from

within the setup part of each Test Method

Implicit Setup removes a lot of Test Code Duplication (page 213) and helps

prevent Fragile Tests (page 239) by moving much of the nonessential interaction

with the SUT out of the very numerous tests and into a much smaller

num-ber of places where it is easier to maintain It can, however, lead to Obscure

Tests (page 186) when a Mystery Guest makes the test fi xture used by each test

less obvious It can also lead to a Fragile Fixture (see Fragile Test) if all tests in

the class do not really need identical test fi xtures

Implementation Notes

The main implementation considerations for Implicit Setup are as follows:

• How do we cause the fi xture setUp method to be called?

• How do we tear the fi xture down?

• How do the Test Methods access the fi xture?

Implicit Setup

Trang 14

Calling the Setup Code

AsetUp method is the most common way to handle Implicit Setup; it consists of

having the Test Automation Framework call the setUp method before each Test

Method Strictly speaking, the setUp method is not the only form of implicit fi

x-ture setup Suite Fixx-ture Setup (page 441), for example, is used to set up and tear

down a Shared Fixture (page 317) that is reused by the Test Methods on a single

Testcase Class In addition, Setup Decorator (page 447) moves the setUp method

to a Decorator [GOF] object installed between the Test Suite Object (page 387)

and the Test Runner (page 377) Both are forms of Implicit Setup because the

setUp logic is not explicit within the Test Method.

Tearing Down the Fixture

The fi xture teardown counterpart of Implicit Setup is Implicit Teardown (page 516)

Anything that we set up in the setUp method that is not automatically cleaned up by

Automated Teardown (page 503) or garbage collection should be torn down in the

correspondingtearDown method

Accessing the Fixture

The Test Methods need to be able to access the test fi xture built in the

suffi cient To communicate between the setUp method and the Test Method,

how-ever, the local variables must be changed into instance variables We must be

careful not to make them class variables as this will result in the potential for a

Shared Fixture (See the sidebar “There’s Always an Exception” on page 384 for a

description of when instance variations do not provide this level of isolation.)

Airport departureAirport = new Airport("Calgary", "YYC");

Airport destinationAirport = new Airport("Toronto", "YYZ");

Flight flight = new Flight( flightNumber,

Trang 15

}

public void testStatus_cancelled() {

// in-line setup

Airport departureAirport = new Airport("Calgary", "YYC");

Airport destinationAirport = new Airport("Toronto", "YYZ");

Flight flight = new Flight( flightNumber,

departureAirport,

destinationAirport);

flight.cancel(); // still part of setup

// exercise SUT and verify outcome

These tests contain a fair amount of Test Code Duplication We can remove this

duplication by refactoring this Testcase Class to use Implicit Setup There are

two refactoring cases to consider

First, when we discover that all tests are doing similar work to set up their

test fi xtures but are not sharing a setUp method, we can do an Extract

Meth-od [Fowler] refactoring of the fi xture setup logic in one of the tests to create

oursetUp method We will also need to convert any local variables to instance

variables (fi elds) that hold the references to the resulting fi xture until the Test

Method can access it

Second, when we discover that a Testcase Class already uses the setUp method to

build the fi xture and has tests that need a different fi xture, we can use an Extract

Class [Fowler] refactoring to move all Test Methods that need a different setup

method to a different class We need to ensure any instance variables that are used

to convey knowledge of the fi xture from the setup method to the Test Methods

are transferred along with the setUp method Sometimes it is simpler to clone the

Testcase Class and delete each test from one or the other copy of the class; we can

then delete from each class any instance variables that are no longer being used

Example: Implicit Setup

In this modifi ed example, we have moved all common fi xture setup code to the

setUp method of our Testcase Class This avoids the need to repeat this code in

each test and makes each test much shorter—which is a good thing

Airport departureAirport;

Airport destinationAirport;

Implicit Setup

Trang 16

Flight flight;

public void setUp() throws Exception{

super.setUp();

departureAirport = new Airport("Calgary", "YYC");

destinationAirport = new Airport("Toronto", "YYZ");

BigDecimal flightNumber = new BigDecimal("999");

flight = new Flight( flightNumber , departureAirport,

public void testGetStatus_cancelled() {

// implicit setup partially overridden

flight.cancel();

// exercise SUT and verify outcome

assertEquals(FlightState.CANCELLED, flight.getStatus());

}

This approach has several disadvantages, which arise because we are not

organizing our Test Methods around a Testcase Class per Fixture (We are using

Testcase Class per Feature here.) All the Test Methods on the Testcase Class

must be able to make do with the same fi xture (at least as a starting point), as

evidenced by the partially overridden fi xture setup in the second test in the

exam-ple The fi xture is also not very obvious in these tests Where does the fl ight come

from? Is there anything special about it? We cannot even rename the instance

variable to communicate the nature of the fl ight better because we are using it to

hold fl ights with different characteristics in each test

Implicit

Setup

Trang 17

Prebuilt Fixture

How do we cause the Shared Fixture to be built before the fi rst

test method that needs it?

We build the Shared Fixture separately from running the tests.

When we choose to use a Shared Fixture (page 317), whether it be for reasons

of convenience or out of necessity, we need to create the Shared Fixture before

we use it

How It Works

We create the fi xture sometime before running the test suite We can create the

fi xture a number of different ways that we’ll discuss later The most important

point is that we don’t need to build the fi xture each time the test suite is run

because the fi xture outlives both the mechanism used to build it and any one

test run that uses it

When to Use It

We can reduce the overhead of creating a Shared Fixture each time a test suite is

run by creating the fi xture only occasionally This pattern is especially

appropri-ate when the cost of constructing the Shared Fixture is extremely high or cannot

be automated easily

Setup

Exercise Verify Teardown

Fixture

Exercise Verify Teardown

SUTTest Runner

Setup

Setup

Exercise Verify Teardown

Fixture

Exercise Verify Teardown

SUTTest Runner

Setup

Also known as:

Prebuilt Context, Test Bed

Prebuilt Fixture

Trang 18

Because of the Manual Intervention (page 250) required to (re)build the fi xture

before the tests are run, we’ll probably end up using the same fi xture several times,

which can lead to Erratic Tests (page 228) caused by shared fi xture pollution We

may be able to avoid these problems by treating the Prebuilt Fixture as an

Immu-table Shared Fixture (see Shared Fixture) and building a Fresh Fixture (page 311)

for anything we plan to modify

The alternatives to a Prebuilt Fixture are a Shared Fixture that is built once

per test run and a Fresh Fixture Shared Fixtures can be constructed using Suite

Fixture Setup (page 441), Lazy Setup (page 435), or Setup Decorator (page 447)

Fresh Fixtures can be constructed using In-line Setup (page 408), Implicit

Setup (page 424), or Delegated Setup (page 411)

Variation: Global Fixture

A Global Fixture is a special case of Prebuilt Fixture where we shared the fi xture

between multiple test automaters The key difference is that the fi xture is globally

visible and not “private” to a particular user This pattern is most commonly

em-ployed when we are using a single shared Database Sandbox (page 650) without

using some form of Database Partitioning Scheme (see Database Sandbox)

The tests themselves can be the same as those used for a basic Prebuilt

Fix-ture; likewise, the fi xture setup is the same as that for a Prebuilt Fixture What’s

different here are the kinds of problems we can encounter Because the fi xture

is now shared among multiple users, each of whom is running a separate Test

Runner (page 377) on a different CPU, we may experience all sorts of

multipro-cessing-related issues The most common problem is a Test Run War (see Erratic

Test) where we see seemingly random results We can avoid this possibility

by adopting some kind of Database Partitioning Scheme or by using Distinct

Generated Values (see Generated Value on page 723) for any fi elds with unique

key constraints

Implementation Notes

The tests themselves look identical to a basic Shared Fixture What’s different is

how the fi xture is set up The test reader won’t be able to fi nd any sign of it either

within the Testcase Class (page 373) or in a Setup Decorator or Suite Fixture

Setup method Instead, the fi xture setup is most probably performed manually

via some kind of database copy operation, by using a Data Loader (see Back

Door Manipulation on page 327) or by running a database population script In

these examples of Back Door Setup (see Back Door Manipulation), we bypass

the SUT and interact with its database directly (See the sidebar “Database as

Prebuilt

Fixture

Trang 19

SUT API?” on page 336 for an example of when the back door really is a front

door.) Another option is to use a Fixture Setup Testcase (see Chained Tests on

page 454) run from a Test Runner either manually or on a regular schedule

Another difference is how the Finder Methods (see Test Utility Method on

page 599) are implemented We cannot just store the results of creating the

objects in a class variable or an in-memory Test Fixture Registry (see Test Helper

on page 643) because we aren’t setting the fi xture up in code within the test

run Two of the more commonly used options available to us are (1) to store

the unique identifi ers generated during fi xture construction in a persistent Test

Fixture Registry (such as a fi le) as we build the fi xture so that the Finder

Meth-ods can retrieve them later and (2) to hard-code the identifi ers in the Finder

Methods We could search for objects/records that meet the Finder Methods’

criteria at runtime, but that approach might result in Nondeterministic Tests

(see Erratic Test) because each test run could end up using a different

object/re-cord from the Prebuilt Fixture This strategy may be a good idea if each test run

modifi es the objects such that they no longer satisfy the criteria Nevertheless, it

may make debugging a failing test rather diffi cult, especially if the failures occur

intermittently because some other attribute of the selected object is different

protected void tearDown() throws Exception {

// Cannot delete any objects because we don't know

// whether this is the last test

}

Note the call to setupStandardAirports in the setUp method The tests use this fi xture

by calling Finder Methods that return objects from the fi xture that match certain

criteria:

1 Of course, there are other ways to set up the Shared Fixture, such as Setup Decorator

and Suite Fixture Setup.

Prebuilt Fixture

Trang 20

public void testGetFlightsByFromAirport_OneOutboundFlight()

One way to convert a Testcase Class from a Standard Fixture (page 305) to a

Prebuilt Fixture is to do an Extract Class [Fowler] refactoring so that the fi xture

is set up in one class and the Test Methods (page 348) are located in another

class Of course, we need to provide a way for the Finder Methods to

deter-mine which objects or records exist in the structure because we won’t be able to

guarantee that any instance or class variables will bridge the time gap between

fi xture construction and fi xture usage

Example: Prebuilt Fixture Test

Here is the resulting Testcase Class that contains the Test Methods Note that it

looks almost identical to the basic Shared Fixture tests.

public void testGetFlightsByFromAirport_OneOutboundFlight()

Trang 21

assertOnly1FlightInDtoList( "Flights at origin",

Example: Fixture Setup Testcase

We may fi nd it to be convenient to set up our Prebuilt Fixture using xUnit This is

simple to do if we already have the appropriate Creation Methods (page 415) or

constructors already defi ned and we have a way to easily persist the objects into

the Database Sandbox In the following example, we call the same method as in

the previous example from the setUp method, except that now the method lives

in the setUp method of a Fixture Setup Testcase that can be run whenever we want

to regenerate the Prebuilt Fixture:

public class FlightManagementFacadeSetupTestcase

extends AbstractFlightManagementFacadeTestCase {

public FlightManagementFacadeSetupTestcase(String name) {

super(name);

}

protected void setUp() throws Exception {

facade = new FlightMgmtFacadeImpl();

helper = new FlightManagementTestHelper();

setupStandardAirportsAndFlights();

saveFixtureInformation();

}

protected void tearDown() throws Exception {

// Leave the Prebuilt Fixture for later use

}

}

Prebuilt Fixture

Trang 22

Note that there are no Test Methods on this Testcase Class and the tearDown

method is empty Here we want to do only the setup—nothing else

Once we created the objects, we saved the information to the database using

the call to saveFixtureInformation; this method persists the objects and saves the

various keys in a fi le so that we can reload them for use from the subsequent

real test runs This approach avoids the need to hard-code knowledge of the

fi xture into Test Methods or Test Utility Methods In the interest of space, I’ll

spare you the details of how we fi nd the “dirty” objects and save the key

infor-mation; there is more than one way to handle this task and any of these tactics

will suffi ce

Example: Prebuilt Fixture Setup Using a Data Population

Script

There are as many ways to build a Prebuilt Fixture in a Database Sandbox as

there are programming languages—everything from SQL scripts to Pearl and

Ruby programs These scripts can contain the data or they can read the data

from a collection of fl at fi les We can even copy the contents of a “golden”

data-base into our Datadata-base Sandbox I’ll leave it as an exercise for you to fi gure out

what’s most appropriate in your particular circumstance

Prebuilt

Fixture

Trang 23

Lazy Setup

How do we cause the Shared Fixture to be built before the

fi rst test method that needs it?

We use Lazy Initialization of the fi xture to create it in the fi rst test that needs it.

Shared Fixtures (page 317) are often used to speed up test execution by reducing

the number of times a complex fi xture needs to be created Unfortunately, a test

that depends on other tests to set up the fi xture cannot be run by itself; it is a

Lonely Test (see Erratic Test on page 228)

We can avoid this problem by having each test use Lazy Setup to set up the

fi xture if it is not already set up

How It Works

We use Lazy Initialization [SBPP] to construct the fi xture in the fi rst test that

needs it and then store a reference to the fi xture in a class variable that every

test can access All subsequently run tests will discover that the fi xture is already

created and that they can reuse it, thereby avoiding the effort of constructing

the fi xture anew

setUp

TestcaseObject

testMethod_n

TestcaseObject

testMethod_1Test

testMethod_n

TestcaseObject

testMethod_1Test

Trang 24

When to Use It

We can use Lazy Setup whenever we need to create a Shared Fixture yet still want to be able to run each test by itself We can also use Lazy Setup instead

of other techniques such as Setup Decorator (page 447) and Suite Fixture

Set-up (page 441) if it is not crucial that the fi xture be torn down For example,

we could use Lazy Setup when we are using a fi xture that can be torn down by

Garbage-Collected Teardown (page 500) We might also use Lazy Setup when

we are using Distinct Generated Values (see Generated Value on page 723) for

all database keys and aren’t worried about leaving extra records lying around

after each test; Delta Assertions (page 485) make this approach possible

The major disadvantage of Lazy Setup is the fact that while it is easy to

discover that we are running the fi rst test and need to construct the fi xture,

it is diffi cult to determine that we are running the last test and the fi xture

should be destroyed Most members of the xUnit family of Test Automation

Frameworks (page 298) do not provide any way to determine this fact other

than by using a Setup Decorator for the entire test suite A few members of the xUnit family support Suite Fixture Setup (NUnit, VbUnit, and JUnit 4.0 and

newer, to name a few), which provides setUp/tearDown “bookends” for a Testcase

Class (page 373) Unfortunately, this ability won’t help us if we are writing our

tests in Ruby, Python, or PLSQL!

Some IDEs and Test Runners (page 377) automatically reload our classes every

time the test suite is run This causes the original class variable to go out of scope,

and the fi xture will be garbage-collected before the new version of the class is run

In these cases there may be no negative consequence of using Lazy Setup.

A Prebuilt Fixture (page 429) is another alternative to setting up the Shared

Fixture for each test run Its use can lead to Unrepeatable Tests (see Erratic Test) if the fi xture is corrupted by some of the tests

Implementation Notes

Because Lazy Setup makes sense only with Shared Fixtures, Lazy Setup carries all the same baggage that comes with Shared Fixtures.

Normally, Lazy Setup is used to build a Shared Fixture to be used by a single

Testcase Class The reference to the fi xture is held in a class variable Things

get a bit trickier if we want to share the fi xture across several Testcase Classes.

We could move both the Lazy Initialization logic and the class variable to a

Testcase Superclass (page 638) but only if our language supports inheritance of

class variables The other alternative is to move the logic and variables to a Test

Helper (page 643)

Lazy Setup

Trang 25

Of course, we could use an approach such as reference counting as a way

to know whether all Test Methods (page 348) have run The challenge would

be to know how many Testcase Objects (page 382) are in the Test Suite

Object (page 387) so that we can compare this number with the number of times

thetearDown method has been called I have never seen anyone do this so I won’t

call it a pattern! Adding logic to the Test Runner to invoke a tearDown method at

the Test Suite Object level would amount to implementing Suite Fixture Setup.

Motivating Example

In this example, we have been building a new fi xture for each Testcase Object:

public void testGetFlightsByFromAirport_OneOutboundFlight()

Not surprisingly, these tests are slow because creating the airports and fl ights

involves a database We can try refactoring these tests to set up the fi xture in the

setUp method (Implicit Setup; see page 424):

protected void setUp() throws Exception {

facade = new FlightMgmtFacadeImpl();

helper = new FlightManagementTestHelper();

setupStandardAirportsAndFlights();

oneOutboundFlight = findOneOutboundFlight();

}

Lazy Setup

Trang 26

protected void tearDown() throws Exception { removeStandardAirportsAndFlights();

}

public void testGetFlightsByOriginAirport_NoFlights_td() throws Exception {

// Fixture Setup BigDecimal outboundAirport = createTestAirport("1OF");

try { // Exercise System List flightsAtDestination1 = facade.getFlightsByOriginAirport(outboundAirport);

// Verify Outcome assertEquals(0,flightsAtDestination1.size());

} finally { facade.removeAirport(outboundAirport);

} }

public void testGetFlightsByFromAirport_OneOutboundFlight() throws Exception {

// Exercise System List flightsAtOrigin = facade.getFlightsByOriginAirport(

oneOutboundFlight.getOriginAirportId());

// Verify Outcome assertOnly1FlightInDtoList( "Flights at origin", oneOutboundFlight, flightsAtOrigin);

}

public void testGetFlightsByFromAirport_TwoOutboundFlights() throws Exception {

FlightDto[] outboundFlights = findTwoOutboundFlightsFromOneAirport();

// Exercise System List flightsAtOrigin = facade.getFlightsByOriginAirport(

outboundFlights[0].getOriginAirportId());

// Verify Outcome assertExactly2FlightsInDtoList( "Flights at origin", outboundFlights, flightsAtOrigin);

}

This doesn’t speed up our tests one bit because the Test Automation Framework

calls the setUp and tearDown methods for each Testcase Object All we have done

is moved the code We need to fi nd a way to set up the fi xture only once per test run

Lazy Setup

Trang 27

Refactoring Notes

We can reduce the number of times we set up the fi xture by converting this test to

Lazy Setup Because the fi xture setup is already handled by the setUp method, we

need simply insert the Lazy Initialization logic into the setUp method so that only

the fi rst test will cause it to be run We must not forget to remove the tearDown logic,

because it will render the Lazy Initialization logic useless if it removes the fi xture

after each Test Method has run! Sorry, but there is nowhere that we can move

this logic to so that it will be run after the last Test Method has completed if our

xUnit family member doesn’t support Suite Fixture Setup.

Example: Lazy Setup

Here is the same test refactored to use Lazy Setup:

protected void setUp() throws Exception {

protected void tearDown() throws Exception {

// Cannot delete any objects because we don't know

// whether this is the last test

}

While there is a tearDown method on AirportFixture, there is no way to know when

to call it! That’s the main consequence of using Lazy Setup Because the variables

are static, they will not go out of scope; hence the fi xture will not be garbage

col-lected until the class is unloaded or reloaded

The tests are unchanged from the Implicit Setup version:

public void testGetFlightsByFromAirport_OneOutboundFlight()

Trang 28

public void testGetFlightsByFromAirport_TwoOutboundFlights() throws Exception {

FlightDto[] outboundFlights = findTwoOutboundFlightsFromOneAirport();

// Exercise System List flightsAtOrigin = facade.getFlightsByOriginAirport(

outboundFlights[0].getOriginAirportId());

// Verify Outcome assertExactly2FlightsInDtoList( "Flights at origin", outboundFlights, flightsAtOrigin);

}

Lazy Setup

Trang 29

Suite Fixture Setup

How do we cause the Shared Fixture to be built before the

fi rst test method that needs it?

We build/destroy the shared fi xture in special methods called by the Test

Automation Framework before/after the fi rst/last Test Method is called.

Shared Fixtures (page 317) are commonly used to reduce the amount of per-test

overhead required to set up the fi xture Sharing a fi xture involves extra test

program-ming effort because we must create the fi xture and have a way of discovering the

fi xture in each test Regardless of how the fi xture is accessed, it must be initialized

(constructed) before it is used

Suite Fixture Setup is one way to initialize the fi xture if all the Test

Meth-ods (page 348) that need it are defi ned on the same Testcase Class (page 373)

How It Works

We implement or override a pair of methods that the Test Automation

Frame-work (page 298) calls automatically The name or annotation of these methods

varies between members of the xUnit family but all work the same way: The

framework calls the Suite Fixture Setup method before it calls the setUp method

for the fi rst Test Method; it calls the Suite Fixture Teardown method after it

calls the tearDown method for the fi nal Test Method (I would have preferred to

Inline Setup Exercise Verify Inline Teardown

Inline Setup Exercise Verify Inline Teardown

Trang 30

say, “method on the fi rst/fi nal Testcase Object” but that isn’t true: NUnit, unlike

other members of the xUnit family, creates only a single Testcase Object See the

sidebar “There’s Always an Exception” on page 384 for details.)

When to Use It

We can use Suite Fixture Setup when we have a test fi xture we wish to share

between all Test Methods of a single Testcase Class and our variant of xUnit

sup-ports this feature This pattern is particularly useful if we need to tear down the

fi xture after the last test is run At the time of writing this book, only VbUnit,

NUnit, and JUnit 4.0 supported Suite Fixture Setup “out of the box.” Nevertheless,

it is not diffi cult to add this capability in most variants of xUnit

If we need to share the fi xture more widely, we must use either a Prebuilt

Fixture (page 429), a Setup Decorator (page 447), or Lazy Setup (page 435)

If we don’t want to share the actual instance of the fi xture but we do want to

share the code to set up the fi xture, we can use Implicit Setup (page 424) or

Delegated Setup (page 411)

The main reason for using a Shared Fixture, and hence Suite Fixture Setup,

is to overcome the problem of Slow Tests (page 253) caused by too many test

fi xture objects being created each time every test is run Of course, a Shared

Fixture can lead to Interacting Tests (see Erratic Test on page 228) or even

a Test Run War (see Erratic Test); the sidebar “Faster Tests Without Shared

Fixtures” (page 319) describes other ways to solve this problem

Implementation Notes

For Suite Fixture Setup to work properly, we must ensure that the fi xture is

remembered between calls to the Test Methods This criterion implies we need to

use a class variable, Registry [PEAA], or Singleton [GOF] to hold the references

to the fi xture (except in NUnit; see the sidebar “There’s Always an Exception”

on page 384) The exact implementation varies from one member of the xUnit

family to the next Here are a few highlights:

• In VbUnit, we implement the interface IFixtureFrame in the Testcase Class, thereby causing the Test Automation Framework (1) to call the IFixture Frame_Create method before the fi rst Test Method is called and (2) to call

theIFixtureFrame_Destroy method after the last Test Method is called

• In NUnit, the attributes [TestFixtureSetUp] and [TestFixtureTearDown] are used inside a test fi xture to designate the methods to be called (1) once

Suite

Fixture

Setup

Trang 31

prior to executing any of the tests in the fi xture and (2) once after all

tests are completed

• In JUnit 4.0 and later, the attribute @BeforeClass is used to indicate that a

method should be run once before the fi rst Test Method is executed The

method with the attribute @AfterClass is run after the last Test Method

is run JUnit allows these methods to be inherited and overridden; the

subclass’s methods are run between the superclass’s methods

Just because we use a form of Implicit Setup to invoke the construction and

destruction of the test fi xture, it doesn’t mean that we should dump all the fi xture

setup logic into the Suite Fixture Setup We can call Creation Methods (page 415)

from the Suite Fixture Setup method to move complex construction logic into

places where it can be tested and reused more easily, such as a Testcase

Super-class (page 638) or a Test Helper (page 643)

public void testGetFlightsByOriginAirport_OneOutboundFlight(){

FlightDto expectedFlight = helper.findOneOutboundFlight();

// Exercise System

Suite Fixture Setup

Trang 32

IList flightsAtOrigin = facade.GetFlightsByOriginAirport(

-Figure 20.1 The calling sequence of Implicit Setup and Test Methods The

setupStandardAirportsAndFlights method is called before each Test Method The

hori-zontal lines delineate the Test Method boundaries

Refactoring Notes

Suppose we want to refactor this example to a Shared Fixture If we don’t care

about destroying the fi xture when the test run is fi nished, we could use Lazy Setup.

Otherwise, we can convert this example to a Suite Fixture Setup strategy by simply

moving our code from the setUp and tearDown methods to the suiteFixtureSetUp and

suiteFixtureTearDown methods, respectively

In NUnit, we use the attributes [TestFixtureSetUp] and [TestFixtureTearDown] to

indicate these methods to the Test Automation Framework If we don’t want

to leave anything in our setUp/tearDown methods, we can simply change the

attributes from [Setup] and TearDown to [TestFixtureSetUp] and [TestFixtureTearDown],

respectively

Example: Suite Fixture Setup

Here’s the result of our refactoring to Suite Fixture Setup:

Trang 33

public void testGetFlightsByOrigin_OneOutboundFlight() {

FlightDto expectedFlight = helper.findOneOutboundFlight();

Trang 34

Figure 20.2 The calling sequence of Suite Fixture Setup and Test Methods

ThesetupStandardAndAirportsAndFlights method is called once only for the Testcase

Class rather than before each Test Method The horizontal lines delineate the

Test Method boundaries.

ThesetUp method is still called before each Test Method, along with the suite

FixtureSetUp method where we are now calling setupStandardAirportsAndFlights to

set up our fi xture So far, this is no different than Lazy Setup; the difference

arises in that removeStandardAirportsAndFlights is called after the last of our Test

Methods.

About the Name

Naming this pattern was tough because each variant of xUnit that implements

it has a different name for it Complicating matters is the fact that the Microsoft

camp uses “test fi xture” to mean more than what the Java/Pearl/Ruby/etc camp

means I landed on Suite Fixture Setup by focusing on the scope of the Shared

Fixture; it is shared across the test suite for one Testcase Class that spawns a

single Test Suite Object (page 387) The fi xture that is built for the Test Suite

Object could be called a “SuiteFixture.”

Further Reading

See http://www.vbunit.com/doc/Advanced.htm for more information on Suite

Fixture Setup as implemented in VbUnit See http://nunit.org for more

informa-tion on Suite Fixture Setup as implemented in NUnit

Suite

Fixture

Setup

Trang 35

Setup Decorator

How do we cause the Shared Fixture to be built before the

fi rst test method that needs it?

We wrap the test suite with a Decorator that sets up the shared test fi xture

before running the tests and tears it down after all tests are done.

If we have chosen to use a Shared Fixture (page 317), whether for reasons of

convenience or out of necessity, and we have chosen not to use a Prebuilt

Fix-ture (page 429), we will need to ensure that the fi xFix-ture is built before each test

run Lazy Setup (page 435) is one strategy we could employ to create the test

fi xture “just in time” for the fi rst test But if it is critical to tear down the fi xture

after the last test, how do we know that all tests have been completed?

How It Works

A Setup Decorator works by “bracketing” the execution of the entire test suite

with a set of matching setUp and tearDown “bookends.” The pattern Decorator

[GOF] is just what we need to make this happen We construct a Setup

Decora-tor that holds a reference to the Test Suite Object (page 387) we wish to decorate

and then pass our Decorator to the Test Runner (page 377) When it is time to

Testcase Object

Testcase Object Fixture

SUT

TestSuiteObject

Inline Setup Exercise Verify Inline Teardown

Inline Setup Exercise Verify Inline Teardown

Inline Setup Exercise Verify Inline Teardown

Inline Setup Exercise Verify Inline Teardown

Trang 36

run the test, the Test Runner calls the run method on our Setup Decorator rather

than the run method on the actual Test Suite Object The Setup Decorator

performs the fi xture setup before calling the run method on the Test Suite Object

and tears down the fi xture after it returns

When to Use It

We can use a Setup Decorator when it is critical that a Shared Fixture be set up

before every test run and that the fi xture is torn down after the run is complete

This behavior may be critical because tests are using Hard-Coded Values (see

Literal Value on page 714) that would cause the tests to fail if they are run

again without cleaning up after each run (Unrepeatable Tests; see Erratic Test on

page 228) Alternatively, this behavior may be necessary to avoid the incremental

consumption of some limited resource, such as our database slowly fi lling up

with data from repeated test runs

We might also use a Setup Decorator when the tests need to change some global

parameter before exercising the SUT and then need to change this parameter back

when they are fi nished Replacing the database with a Fake Database (see Fake

Object on page 551) in an effort to avoid Slow Tests (page 253) is one common

reason for taking this approach; setting global switches to a particular confi

gura-tion is another Setup Decorators are installed at runtime, so nothing stops us

from using several different decorators on the same test suite at different times (or

even the same time)

As an alternative to a Setup Decorator, we can use Suite Fixture Setup (page 441) if we only want to share the fi xture across the tests in a single Testcase

Class (page 373) and our member of the xUnit family supports this behavior If

it is not essential that the fi xture be torn down after every test run, we could use

Lazy Setup instead

Implementation Notes

A Setup Decorator consists of an object that sets up the fi xture, delegates test

execution to the test suite to be run, and then executes the code to tear down the

fi xture To better line up with the normal xUnit calling conventions, we typically

put the code that constructs the test fi xture into a method called setUp and the

code that tears down the fi xture into a method called tearDown Then our Setup

Decorator’srun logic consists of three lines of code:

Setup

Decorator

Trang 37

There are several ways to build the Setup Decorator.

Variation: Abstract Setup Decorator

Many members of the xUnit family of Test Automation Frameworks (page 298)

provide a reusable superclass that implements a Setup Decorator This class

usually implements the setUp/run/tearDown sequence as a Template Method [GOF]

All we have to do is to subclass this class and implement the setUp and tearDown

methods as we would in a normal Testcase Class When instantiating our Setup

Decorator class, we pass the Test Suite Object we are decorating as the

construc-tor argument

Variation: Hard-Coded Setup Decorator

If we need to build our Setup Decorator from scratch, the “simplest thing that

could possibly work” is to hard-code the name of the decorated class in the

to act as the Test Suite Factory (see Test Enumeration on page 399) for the

decorated suite

Variation: Parameterized Setup Decorator

If we want to reuse the Setup Decorator for different test suites, we can

param-eterize its constructor method with the Test Suite Object to be run This means

that the setup and teardown logic can be coded within the Setup Decorator,

thereby eliminating the need for a separate Test Helper (page 643) class just to

reuse the setup logic across tests

Variation: Decorated Lazy Setup

One of the main drawbacks of using a Setup Decorator is that tests cannot

be run by themselves because they depend on the Setup Decorator to set up

the fi xture We can work around this requirement by augmenting the Setup

Decorator with Lazy Setup in the setUp method so that an undecorated Testcase

Object (page 382) can construct its own fi xture The Testcase Object can also

remember that it built its own fi xture and destroy it in the tearDown method This

functionality could be implemented on a generic Testcase Superclass (page 638)

so that it has to be built and tested just once

Setup Decorator

Trang 38

The only other alternative is to use a Pushdown Decorator That would negate any test speedup the Shared Fixture bought us, however, so this approach can

be used only in those cases when we use the Setup Decorator for reasons other

than setting up a Shared Fixture.

Variation: Pushdown Decorator

One of the main drawbacks of using a Setup Decorator is that tests cannot be

run by themselves because they depend on the Setup Decorator to set up the

fi xture One way we can circumvent this obstacle is to provide a means to push

the decorator down to the level of the individual tests rather than the whole test

suite This step requires a few modifi cations to the TestSuite class to allow the

Setup Decorator to be passed down to where the individual Testcase Objects

are constructed during the Test Discovery (page 393) process As each object is

created from the Test Method (page 348), it is wrapped in the Setup Decorator

before it is added to the Test Suite Object’s collection of tests

Of course, this negates one of the major sources of the speed advantage created

by using a Setup Decorator by forcing a new test fi xture to be built for each

test See the sidebar “Faster Tests Without Shared Fixtures” on page 319 for

other ways to address the test execution speed issue

Motivating Example

In this example, we have a set of tests that use Lazy Setup to build the Shared

Fixture and Finder Methods (see Test Utility Method on page 599) to fi nd the

objects in the fi xture We have discovered that the leftover fi xture is causing

Unrepeatable Tests, so we want to clean up properly after the last test has fi

protected void tearDown() throws Exception {

// Cannot delete any objects because we don't know

// whether this is the last test

}

Setup

Decorator

Trang 39

Because there is no easy way to accomplish this goal with Lazy Setup, we

must change our fi xture setup strategy One option is to use a Setup Decorator

instead.

Refactoring Notes

When creating a Setup Decorator, we can reuse the exact same fi xture setup

logic; we just need to call it at a different time Thus this refactoring consists

mostly of moving the call to the fi xture setup logic from the setUp method on the

Testcase Class to the setUp method of a Setup Decorator class Assuming we have

an Abstract Setup Decorator available to subclass, we can create our new

sub-class and provide concrete implementations of the setUp and tearDown methods

If our instance of xUnit does not support Setup Decorator directly, we can

create our own Setup Decorator superclass by building a single-purpose Setup

Decorator and then introducing a constructor parameter and instance variable

to hold the test suite to be run Finally, we do an Extract Superclass [Fowler]

refactoring to create our reusable superclass

Example: Hard-Coded Setup Decorator

In this example, we have moved all of the setup logic to the setUp method of

a Setup Decorator that inherits its basic functionality from an Abstract Setup

Decorator We have also written some fi xture teardown logic in the tearDown

method so that we clean up the fi xture after the entire test suite has been run

public class FlightManagementTestSetup extends TestSetup {

private FlightManagementTestHelper helper;

public FlightManagementTestSetup() {

// Construct the Test Suite Object to be decorated and

// pass it to our Abstract Setup Decorator superclass

Trang 40

public static Test suite() {

// Return an instance of this decorator class

return new FlightManagementTestSetup();

}

}

Because this is a Hard-Coded Setup Decorator, the call to the Test Suite Factory

that builds the actual Test Suite Object is hard-coded inside the constructor The

suite method just calls the constructor

Example: Parameterized Setup Decorator

To make our Setup Decorator reusable with several different test suites, we need

to do an Introduce Parameter [JBrains] refactoring on the name of the Test Suite

Factory inside the constructor:

public class ParameterizedFlightManagementTestSetup extends TestSetup {

private FlightManagementTestHelper helper =

To make it easy for the Test Runner to create our test suite, we also need to

cre-ate a Test Suite Factory that calls the Setup Decorator’s constructor with the Test

Suite Object to be decorated:

public class DecoratedFlightManagementFacadeTestFactory {

public static Test suite() {

// Return a new Test Suite Object suitably decorated

return new ParameterizedFlightManagementTestSetup(

Ngày đăng: 14/08/2014, 01:20

TỪ KHÓA LIÊN QUAN