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 1brittle—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 2With 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 3This page intentionally left blank
Trang 4The 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 5Period!” 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 6Spreading 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 7on 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 8We 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 9This page intentionally left blank
Trang 10Appendix 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 11Test 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 12Tests 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 13oneOf (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 14The 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 15oneOf(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 16Actions
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 17States 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