As an afterword to the book, we had Tim Mackinnon write up a piece on how we got here. In compensation for having been so quiet for a while, here's that chapter.
The ideas and concepts behind mock objects didn't materialise in a single day. There's a long history of experimentation, discussion, and collaboration between many different developers who have taken the seed of an idea and grown it into something more profound. The final result should help you with your software development; but the background story of “The Making of Mock Objects” is also interesting—and a testament to the dedication of the people involved. I hope revisiting this history will inspire you too to challenge your thoughts on what is possible and to experiment with new practices.
The story began on a roundabout near Archway station in London in late 1999. That evening, several members of a London-based software architecture group met to discuss topical issues in software. The discussion turned to experiences with Agile Software Development and I mentioned the impact that writing tests seemed to be having on our code. This was before the first Extreme Programming book had been published, and teams like ours were still exploring how to do test-driven development—including what constituted a good test. In particular, I had noticed a tendency to add “getter” methods to our objects to facilitate testing. This felt wrong, since it could be seen as violating object-oriented principles, so I was interested in the thoughts of the other members. The conversation was quite lively—mainly centering on the tension between pragmatism in testing and pure object-oriented design. We also had a recent example of a colleague, Oli Bye, stubbing out the Java Servlet API for testing a web application without a server.
I particularly remember from that evening a crude diagram of an onion and its metaphor of the many layers of software, along with the mantra “No Getters! 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 the composition 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 Connextra started putting the idea into practice. We removed the getters from sections of our code and used a compositional strategy by adding constructors that took the objects we wanted to test via getters as parameters. At first this felt cumbersome, and our two recent graduate 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 Java language 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 our objects, and we frequently had variables with names like
expectedServiceName in our injected objects. On the other hand, when our tests failed we were tired of stepping through in a debugger to see what went wrong. We started adding variables with names like
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 what the problem was.
Over the course of several weeks we refactored these ideas into a group of classes:
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 there wasn't really much to describe. One afternoon, Peter Marks decided that we should come up with name for what we were doing—so we could at least package the 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.
Spreading the Word
Around this time, we also started the London Extreme Tuesday Club (XTC) to share experiences of Extreme Programming with other teams. During one meeting, I described our refactoring experiments and explained that I felt that it helped our junior developers write better object-oriented code. I finished the story by saying, “But this is such an obvious technique that I'm sure most people do it eventually 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 a great paper if we could sort the wood from the trees, so we decided to collaborate with another XTC member (Philip Craig) and write something for the XP2000 conference. 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. We backported new concepts we discovered to the original Connextra codebase to validate their effectiveness. This was an exciting time and I recall that it took many 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 like when using them to drive great code, but describing this experience to other developers who weren't part of the XTC was still challenging.
The XP2000 paper and the initial mock objects library had a mixed reception—for some it was revolutionary, for others it was unnecessary overhead. In retrospect, the fact that Java didn’t have good reflection when we started meant that many of the steps were manual, or augmented with code generation tools. This turned people off—they couldn't separate the idea from the implementation.
The story continues when Nat Pryce took the ideas and implemented them in Ruby. He exploited Ruby's reflection to write expectations directly into the test as blocks. Influenced by his PhD work on protocols between components, his library changed the emphasis from asserting parameter values to asserting messages sent between objects. Nat then ported his implementation to Java, using the new Proxy type in Java 1.3 and defining expectations with “constraint” objects. When Nat showed us this work, it immediately clicked. He donated his library to the mock objects project and visited the Connextra offices where we worked together to add features that the Connextra developers needed.
With Nat in the office where mock objects were being used constantly, we were driven to use his improvements to provide more descriptive failure messages. We had seen our developers getting bogged down when the reason for a test failure was not obvious enough (later, we observed that this was often a hint that an object had too many responsibilities). Now, constraints allowed us to write tests that were more expressive and provided better failure diagnostics, as the constraint objects could explain what went wrong. For example, a failure 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 introduced new requirements. We started adding more and more options to the API until, eventually, it became too complicated to maintain—especially as we had 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 a Smalltalk cascade—multiple calls to the same object.
Then Steve noticed that in a statically typed language like Java, a cascade could return a chain of interfaces to control when methods are made available to the caller—in effect, we could use types to encode a workflow. Steve also wanted to improve the programming experience by guiding the new generation of IDEs to prompt with the “right” completion options. Over the course of a year, Steve and Nat, with much input from the rest of us, pushed the idea hard to produce jMock, an expressive API over our original Dynamock framework. This was also ported 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.
Through our experience in Connextra and other companies, and through giving many presentations, we improved our understanding and communication of the ideas 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 interfaces between objects. At the time, we were struggling to promote the idea of using mock 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 conceptual barriers with his simple heuristic of “Only mock types you own.”
We took all these ideas and wrote a second conference paper, “Mock Roles not Objects”. Our initial description had focused too much on implementation, whereas the critical idea was that the technique emphasizes the roles that objects play for each other. When developers are using mock objects well, I observe them drawing diagrams of what they want to test, or using CRC cards to roleplay relationships“these then translate nicely into mock objects and tests that drive the required code. Since then, Nat and Steve have reworked jMock to produce jMock2, and Joe has extracted constraints into the Hamcrest library (now adopted by JUnit). There’s also now a wide selection of mock object libraries, in many different languages.
The results have been worth the effort. I think we can finally say that there is now a well-documented and polished technique that helps you write better software. From those humble “no getters” beginnings, this book summarizes years of experience from all of us who have collaborated, and adds Steve and Nat's language expertise and careful attention to detail to produce something that is greater than the sum of its parts.
Thanks for a very nice read!
>the critical idea was that the technique emphasizes the roles that objects play for each other
I really like this idea but it hasn't been totally obvious to me working with mock objects on my own or in code written by others. Often the mocking has been quite implementation specific and done on the lowest level possible.
@Jonas. Thanks. One of our great frustrations has been how the concept has been so misunderstood -- largely because our own early misconceptions seem to have been the ones that spread. It takes a little effort and experience to learn to see where to put the seams.
Minor correction for you - "using the new Proxy type in Java 1.1" - Proxy was introduced in Java 1.3
I know this because MockMaker (a mock object code generator used at Connextra before "Another Generation") had to use code generation as we were using VisualAge for Java at Connextra which only supported Java 1.1.8 (or thereabouts) at the time.
I am wondering why you chose to exclude EasyMock from your history of mock objects, considering EasyMock was the first mock object library in today's sense. Before, there were only hand-written mock objects using the mockobjects library, and generating code for mock objects via MockMaker.
EasyMock was the first library that provided dynamic mock objects. It's first release dates back to June 28, 2001, preceeding both Ruby/Mock (Sep 2, 2001), and DynaMock.
Actually, you're right. It's a bit late now, but I think we just forgot the sequence. Sorry.
Post a Comment