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

Tài liệu Growing Object-Oriented Software, Guided by Tests- P8 doc

35 323 1
Tài liệu đã được kiểm tra trùng lặp

Đ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 đề Testing Asynchronous Code
Trường học Unknown
Chuyên ngành Software Testing & Development
Thể loại Essay
Định dạng
Số trang 35
Dung lượng 0,93 MB

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

Nội dung

It was an interesting collision of opinions, and the emphasis on composition—now referred to as dependency injection—gave us a technique for eliminating the getters we were “pragmaticall

Trang 1

brittle—they would misreport if the system changes the assumptions they’ve beenbuilt on One response is to add a test to confirm those expectations—in thiscase, perhaps a stress test to confirm event processing order and alert the team

if circumstances change That said, there should already be other tests that confirmthose assumptions, so it may be enough just to associate these tests, for example

by grouping them in the same test package

Distinguish Synchronizations and Assertions

We have one mechanism for synchronizing a test with its system and for makingassertions about that system—wait for an observable condition and time out if

it doesn’t happen The only difference between the two activities is our tation of what they mean As always, we want to make our intentions explicit,but it’s especially important here because there’s a risk that someone may look

interpre-at the test linterpre-ater and remove whinterpre-at looks like a duplicinterpre-ate assertion, accidentallyintroducing a race condition

We often adopt a naming scheme to distinguish between synchronizations andassertions For example, we might have waitUntil() and assertEventually()methods to express the purpose of different checks that share an underlyingimplementation

Alternatively, we might reserve the term “assert” for synchronous tests anduse a different naming conventions in asynchronous tests, as we did in the AuctionSniper example

Externalize Event Sources

Some systems trigger their own events internally The most common example isusing a timer to schedule activities This might include repeated actions that runfrequently, such as bundling up emails for forwarding, or follow-up actions thatrun days or even weeks in the future, such as confirming a delivery date

Hidden timers are very difficult to work with because they make it hard to tellwhen the system is in a stable state for a test to make its assertions Waiting for

a repeated action to run is too slow to “succeed fast,” to say nothing of an actionscheduled a month from now We also don’t want tests to break unpredictablybecause of interference from a scheduled activity that’s just kicked in Trying totest a system by coinciding timers is just too brittle

The only solution is to make the system deterministic by decoupling it fromits own scheduling We can pull event generation out into a shared service that

is driven externally For example, in one project we implemented the system’sscheduler as a web service System components scheduled activities by makingHTTP requests to the scheduler, which triggered activities by making HTTP

“postbacks.” In another project, the scheduler published notifications onto amessage bus topic that the components listened to

Chapter 27 Testing Asynchronous Code

326

Trang 2

With this separation in place, tests can step the system through its behavior

by posing as the scheduler and generating events deterministically Now we canrun system tests quickly and reliably This is a nice example of a testing require-ment leading to a better design We’ve been forced to abstract out scheduling,which means we won’t have multiple implementations hidden in the system

Usually, introducing such an event infrastructure turns out to be useful formonitoring and administration

There’s a trade-off too, of course Our tests are no longer exercising the entiresystem We’ve prioritized test speed and reliability over fidelity We compensate

by keeping the scheduler’s API as simple as possible and testing it rigorously(another advantage) We would probably also write a few slow tests, running in

a separate build, that exercise the whole system together including the realscheduler

327

Externalize Event Sources

Trang 3

This page intentionally left blank

Trang 4

The ideas and concepts behind mock objects didn’t materialise in a single day.

There’s a long history of experimentation, discussion, and collaboration betweenmany different developers who have taken the seed of an idea and grown it intosomething more profound The final result—the topic of this book—should helpyou with your software development; but the background story of “The Making

of Mock Objects” is also interesting—and a testament to the dedication of thepeople involved I hope revisiting this history will inspire you too to challengeyour thoughts on what is possible and to experiment with new practices

Origins

The story began on a roundabout1 near Archway station in London in late 1999

That evening, several members of a London-based software architecture group2

met to discuss topical issues in software The discussion turned to experienceswith Agile Software Development and I mentioned the impact that writing testsseemed to be having on our code This was before the first Extreme Programmingbook had been published, and teams like ours were still exploring how to dotest-driven development—including what constituted a good test In particular,

I had noticed a tendency to add “getter” methods to our objects to facilitatetesting This felt wrong, since it could be seen as violating object-oriented princi-ples, so I was interested in the thoughts of the other members The conversationwas quite lively—mainly centering on the tension between pragmatism in testingand pure object-oriented design We also had a recent example of a colleague,

Trang 5

Period!” The discussion revolved around how to safely peel back and test layers

of that onion without impacting its design The solution was to focus on thecomposition of software components (the group had discussed Brad Cox’s ideas

on software components many times before) It was an interesting collision of

opinions, and the emphasis on composition—now referred to as dependency injection—gave us a technique for eliminating the getters we were “pragmatically”

adding to objects so we could write tests for them

The following day, our small team at Connextra4 started putting the idea intopractice We removed the getters from sections of our code and used a composi-tional strategy by adding constructors that took the objects we wanted to testvia getters as parameters At first this felt cumbersome, and our two recentgraduate recruits were not convinced I, however, had a Smalltalk background,

so to me the idea of composition and delegation felt right Enforcing a “no getters”

rule seemed like a way to achieve a more object-oriented feeling in the Javalanguage we were using

We stuck to it for several days and started to see some patterns emerging More

of our conversations were about expecting things to happen between ourobjects, and we frequently had variables with names like expectedURL andexpectedServiceName in our injected objects On the other hand, when our testsfailed we were tired of stepping through in a debugger to see what went wrong

We started adding variables with names like actualURL and actualServiceName

to allow the injected test objects to throw exceptions with helpful messages

Printing the expected and actual values side-by-side showed us immediately whatthe problem was

Over the course of several weeks we refactored these ideas into a group ofclasses: ExpectationValue for single values, ExpectationList for multiple values

in a particular order, and ExpectationSet for unique values in any order Later,Tung Mac also added ExpectationCounter for situations where we didn’t want

to specify explicit values but just count the number of calls It started to feel as

if something interesting was happening, but it seemed so obvious to me that therewasn’t really much to describe One afternoon, Peter Marks decided that weshould come up with name for what we were doing—so we could at least packagethe code—and, after a few suggestions, proposed “mock.” We could use it both

as a noun and a verb, and it refactored nicely into our code, so we adopted it

3 Initially drawn by John Nolan.

4 The team consisted of Tim Mackinnon, Tung Mac, and Matthew Cooke, with direction from Peter Marks and John Nolan Connextra is now part of Bet Genius.

Afterword A Brief History of Mock Objects

330

Trang 6

Spreading the Word

Around this time, we5 also started the London Extreme Tuesday Club (XTC) toshare experiences of Extreme Programming with other teams During one meeting,

I described our refactoring experiments and explained that I felt that it helpedour junior developers write better object-oriented code I finished the story bysaying, “But this is such an obvious technique that I’m sure most people do iteventually anyway.” Steve pointed out that the most obvious things aren’t always

so obvious and are usually difficult to describe He thought this could make agreat paper if we could sort the wood from the trees, so we decided to collaboratewith another XTC member (Philip Craig) and write something for the XP2000conference If nothing else, we wanted to go to Sardinia

We began to pick apart the ideas and give them a consistent set of names,studying real code examples to understand the essence of the technique Webackported new concepts we discovered to the original Connextra codebase tovalidate their effectiveness This was an exciting time and I recall that it tookmany late nights to refine our ideas—although we were still struggling to come

up with an accurate “elevator pitch” for mock objects We knew what it felt likewhen using them to drive great code, but describing this experience to otherdevelopers who weren’t part of the XTC was still challenging

The XP2000 paper [Mackinnon00] and the initial mock objects library had amixed reception—for some it was revolutionary, for others it was unnecessaryoverhead In retrospect, the fact that Java didn’t have good reflection when westarted meant that many of the steps were manual, or augmented with codegeneration tools.6 This turned people off—they couldn’t separate the idea fromthe implementation

li-to add features that the Connextra developers needed

331

Another Generation

Trang 7

on a stringBegins constraint could produce a message like:

Expected a string parameter beginning with "http"

but was called with a value of "ftp.domain.com"

We released the new improved version of Nat’s library under the name Dynamock

As we improved the library, more programmers started using it, which duced new requirements We started adding more and more options to the APIuntil, eventually, it became too complicated to maintain—especially as we had

intro-to support multiple versions of Java Meanwhile, Steve tired of the the duplication

in the syntax required to set up expectations, so he introduced a version of aSmalltalk cascade—multiple calls to the same object

Then Steve noticed that in a statically typed language like Java, a cascade couldreturn a chain of interfaces to control when methods are made available to thecaller—in effect, we could use types to encode a workflow Steve also wanted toimprove the programming experience by guiding the new generation of IDEs

to prompt with the “right” completion options Over the course of a year, Steveand Nat, with much input from the rest of us, pushed the idea hard to producejMock, an expressive API over our original Dynamock framework This was alsoported to C# as NMock At some point in this process, they realized that

they were actually writing a language in Java which could be used to write

expectations; they wrote this up later in an OOPLSA paper [Freeman06]

Consolidation

Through our experience in Connextra and other companies, and through givingmany presentations, we improved our understanding and communication of theideas of mock objects Steve (inspired by some of the early lean software material)coined the term “needs-driven development,” and Joe Walnes, another colleague,drew a nice visualisation of islands of objects communicating with each other

Joe also had the insight of using mock objects to drive the design of interfacesbetween objects At the time, we were struggling to promote the idea of usingmock objects as a design tool; many people (including some authors) saw it only

as a technique for speeding up unit tests Joe cut through all the conceptualbarriers with his simple heuristic of “Only mock types you own.”

7 Later, Steve talked Charlie Poole into including constraints in NUnit It took some extra years to have matchers (the latest version of constraints) adopted by JUnit.

Afterword A Brief History of Mock Objects

332

Trang 8

We took all these ideas and wrote a second conference paper, “Mock Rolesnot Objects” [Freeman04] Our initial description had focused too much on im-plementation, whereas the critical idea was that the technique emphasizes theroles that objects play for each other When developers are using mock objectswell, I observe them drawing diagrams of what they want to test, or using CRCcards to roleplay relationships—these then translate nicely into mock objects andtests that drive the required code

Since then, Nat and Steve have reworked jMock to produce jMock2, and Joehas extracted constraints into the Hamcrest library (now adopted by JUnit)

There’s also now a wide selection of mock object libraries, in many differentlanguages

The results have been worth the effort I think we can finally say that there isnow a well-documented and polished technique that helps you write better soft-ware From those humble “no getters” beginnings, this book summarizes years

of experience from all of us who have collaborated, and adds Steve and Nat’slanguage expertise and careful attention to detail to produce something that isgreater than the sum of its parts

333

Consolidation

Trang 9

This page intentionally left blank

Trang 10

Appendix A

jMock2 Cheat Sheet

Introduction

We use jMock2 as our mock object framework throughout this book This

chapter summarizes its features and shows some examples of how to use them

We’re using JUnit 4.6 (we assume you’re familiar with it); jMock also supportsJUnit3 Full documentation is available at www.jmock.org

We’ll show the structure of a jMock unit test and describe what its features

do Here’s a whole example:

private final Turtle turtle = context.mock(Turtle.class);

@Test public void goesAMinimumDistance() { final Turtle turtle2 = context.mock(Turtle.class, "turtle2");

final TurtleDriver driver = new TurtleDriver(turtle1, turtle2); // set up

context.checking(new Expectations() {{ // expectations

Trang 11

Test Fixture Class

First, we set up the test fixture class by creating its Mockery

[…]

}

For the object under test, a Mockery represents its context—the neighboring

objects it will communicate with The test will tell the mockery to createmock objects, to set expectations on the mock objects, and to check at the end

of the test that those expectations have been met By convention, the mockery isstored in an instance variable named context

A test written with JUnit4 does not need to extend a specific base class butmust specify that it uses jMock with the @RunWith(JMock.class) attribute.1 Thistells the JUnit runner to find a Mockery field in the test class and to assert (at theright time in the test lifecycle) that its expectations have been met This requiresthat there should be exactly one mockery field in the test class The classJUnit4Mockery will report expectation failures as JUnit4 test failures

Creating Mock Objects

This test uses two mock turtles, which we ask the mockery to create The first is

a field in the test class:

private final Turtle turtle = context.mock(Turtle.class);

The second is local to the test, so it’s held in a variable:

final Turtle turtle2 = context.mock(Turtle.class, "turtle2");

The variable has to be final so that the anonymous expectations block has access

to it—we’ll return to this soon This second mock turtle has a specified name,turtle2 Any mock can be given a name which will be used in the report if thetest fails; the default name is the type of the object If there’s more than one mockobject of the same type, jMock enforces that only one uses the default name; theothers must be given names when declared This is so that failure reports canmake clear which mock instance is which when describing the state of the test

1 At the time of writing, JUnit was introducing the concept of Rule We expect to extend the jMock API to adopt this technique.

Appendix A jMock2 Cheat Sheet

336

Trang 12

Tests with Expectations

A test sets up its expectations in one or more expectation blocks, for example:

What’s with the Double Braces?

The most disconcerting syntax element in jMock is its use of double braces in an expectations block It’s a hack, but with a purpose If we reformat an expectations block, we get this:

context.checking(new Expectations() { {

oneOf (turtle).turn(45);

} });

We’re passing to the checking() method an anonymous subclass of Expectations (first set of braces) Within that subclass, we have an instance initialization block (second set of braces) that Java will call after the constructor Within the initialization block, we can reference the enclosing Expectations object, so oneOf() is actually

an instance method—as are all of the expectation structure clauses we describe

in the next section.

The purpose of this baroque structure is to provide a scope for building up expectations All the code in the expectation block is defined within an anonymous instance of Expectations, which collects the expectation components that the code generates The scoping to an instance allows us to make this collection im- plicit, which requires less code It also improves our experience in the IDE, since code completion will be more focused, as in Figure A.1.

Referring back to the discussion in “Building Up to Higher-Level Programming”

(page 65), Expectations is an example of the Builder pattern.

337

Tests with Expectations

Trang 13

oneOf (turtle).turn(45); // The turtle must be told exactly once to turn 45 degrees.

atLeast(1).of (turtle).stop(); // The turtle must be told at least once to stop.

allowing (turtle).flashLEDs(); // The turtle may be told any number of times,

// including none, to flash its LEDs.

allowing (turtle).queryPen(); will(returnValue(PEN_DOWN));

// The turtle may be asked about its pen any // number of times and will always return PEN_DOWN.

ignoring (turtle2); // turtle2 may be told to do anything This test ignores it.

Invocation Count

The invocation count is required to describe how often we expect a call to be

made during the run of the test It starts the definition of an expectation

Trang 14

The invocation is allowed any number of times including none Theseclauses are equivalent to atLeast(0).of, but we use them to highlight that

the expectation is a stub—that it’s there to get the test through to the

interesting part of the behavior

never

The invocation is not expected This is the default behavior if no expectationhas been set We use this clause to emphasize to the reader of a test that aninvocation should not be called

allowing, ignoring, and never can also be applied to an object as a whole

For example, ignoring(turtle2) says to allow all calls to turtle2 Similarly,never(turtle2) says to fail if any calls are made to turtle2 (which is the same

as not specifying any expectations on the object) If we add method expectations,

we can be more precise, for example:

allowing(turtle2).log(with(anything()));

never(turtle2).stop();

will allow log messages to be sent to the turtle, but fail if it’s told to stop Inpractice, while allowing precise invocations is common, blocking individualmethods is rarely useful

Methods

Expected methods are specified by calling the method on the mock object within

an expectation block This defines the name of the method and what argumentvalues are acceptable Values passed to the method in an expectation will becompared for equality:

339

Expectations

Trang 15

oneOf(calculator).add(with(lessThan(15)), with(any(int.class)));

// matches add() called with a number less than 15 and any other number

Either all the arguments must be matchers or all must be values:

oneOf(calculator).add(with(lessThan(15)), 22); // this doesn't work!

The argument is any value, including null The type argument is required

to force Java to type-check the argument at compile time

a(Class<T> type) an(Class<T> type)

The argument is an instance of type or of one of its subtypes

The argument matches all of the matchers m1, m2, m3, […]

More matchers are available from static factory methods of the HamcrestMatchers class, which can be statically imported into the test class For moreprecision, custom matchers can be written using the Hamcrest library

Appendix A jMock2 Cheat Sheet

340

Trang 16

Actions

An expectation can also specify an action to perform when it is matched, byadding a will() clause after the invocation For example, this expectation willreturn PEN_DOWN when queryPen() is called:

allowing (turtle).queryPen(); will(returnValue(PEN_DOWN));

jMock provides several standard actions, and programmers can provide customactions by implementing the Action interface The standard actions are:

Throw exception e when called

will(doAll(a1, a2, […], an))

Perform all the actions a1 to an on every invocation

Sequences

The order in which expectations are specified does not have to match the order

in which their invocations are called If invocation order is significant, it can beenforced in a test by adding a Sequence A test can create more than one sequenceand an expectation can be part of more than once sequence at a time The syntaxfor creating a Sequence is:

Sequence sequence-variable = context.sequence("sequence-name");

To expect a sequence of invocations, create a Sequence object, write the tations in the expected order, and add an inSequence() clause to each relevantexpectation Expectations in a sequence can have any invocation count Forexample:

expec-341

Expectations

Trang 17

States state-machine-name = context.states("state-machine-name").startsAs("initial-state");

The initial state is optional; if not specified, the state machine starts in an unnamedinitial state

Add these clauses to expectations to constrain them to match invocations in

a given state, or to switch the state of a state machine after an invocation:

final States pen = context.states("pen").startsAs("up");

allowing (turtle).queryColor(); will(returnValue(BLACK));

allowing (turtle).penDown(); then(pen.is("down"));

allowing (turtle).penUp(); then(pen.is("up"));

atLeast(1).of (turtle).forward(15); when(pen.is("down"));

one (turtle).turn(90); when(pen.is("down"));

one (turtle).forward(10); when(pen.is("down"));

}}

Notice that expectations with states do not define a sequence; they can be bined with Sequence constraints if order is significant As before, the queryColor()call is not included in the states, and so can be called at any time

com-Appendix A jMock2 Cheat Sheet

342

Ngày đăng: 24/12/2013, 06:17

TỪ KHÓA LIÊN QUAN