Mocks Aren't Stubs

来源:互联网 发布:统计汇总报表软件 编辑:程序博客网 时间:2024/05/18 00:57

The term 'Mock Objects' has become a popular one todescribe special case objects that mimic real objects fortesting. Most language environments now have frameworks that make iteasy to create mock objects. What's often not realized, however, isthat mock objects are but one form of special case test object, onethat enables a different style of testing. In this article I'llexplain how mock objects work, how they encourage testing based onbehavior verification, and how the community around them uses themto develop a different style of testing.

02 January 2007

Photo of Martin Fowler

Martin Fowler

Translations: French ·Italian ·Spanish · Portuguese · Korean ·
Find similar articles to this by looking at these tags:popular ·testing

Contents

  • Regular Tests
  • Tests with Mock Objects
    • Using EasyMock
  • The Difference Between Mocks and Stubs
  • Classical and Mockist Testing
  • Choosing Between the Differences
    • Driving TDD
    • Fixture Setup
    • Test Isolation
    • Coupling Tests to Implementations
    • Design Style
  • So should I be a classicist or a mockist?
  • Final Thoughts

I first came across the term "mock object" a few years ago in theXP community. Since then I've run into mock objects more and more.Partly this is because many of the leading developers of mock objectshave been colleagues of mine at ThoughtWorks at various times. Partly it'sbecause I see them more and more in the XP-influenced testingliterature.

But as often as not I see mock objects described poorly. In particular Isee them often confused with stubs - a common helper to testingenvironments. I understand this confusion - I saw them as similar fora while too, but conversations with the mock developers have steadilyallowed a little mock understanding to penetrate my tortoiseshellcranium.

This difference is actually two separate differences. On theone hand there is a difference in how test results are verified: adistinction between state verification and behaviorverification. On the other hand is a whole different philosophy tothe way testing and design play together, which I term here as theclassical and mockist styles of Test Driven Development.

(In the earlier version of this essay I had realized there was adifference, but combined the two differences together. Since thenmy understanding has improved, and as a result it's time to updatethis essay. If you haven't read the previous essay you can ignoremy growing pains, I've written this essay as if the oldversion doesn't exist. But if you are familiar with the old version youmay find it helpful to note that I've broken the old dichotomy ofstate based testing and interaction based testing into thestate/behavior verification dichotomy and the classical/mockistTDD dichotomy. I've also adjusted my vocabulary to match that ofthe Gerard Meszaros'sxUnit patterns book.)


Regular Tests

I'll begin by illustrating the two styles with a simpleexample. (The example is in Java, but the principles make sense withany object-oriented language.) We want to take an order object and fill it from a warehouseobject. The order is very simple, with only one product and aquantity. The warehouse holds inventories of different products. Whenwe ask an order to fill itself from a warehouse there are two possibleresponses. If there's enough product in the warehouse to fill theorder, the order becomes filled and the warehouse's amount of theproduct is reduced by the appropriate amount. If there isn't enoughproduct in the warehouse then the order isn't filled and nothinghappens in the warehouse.

These two behaviors imply a couple of tests, these look likepretty conventional JUnit tests.

public class OrderStateTester extends TestCase {  private static String TALISKER = "Talisker";  private static String HIGHLAND_PARK = "Highland Park";  private Warehouse warehouse = new WarehouseImpl();  protected void setUp() throws Exception {    warehouse.add(TALISKER, 50);    warehouse.add(HIGHLAND_PARK, 25);  }  public void testOrderIsFilledIfEnoughInWarehouse() {    Order order = new Order(TALISKER, 50);    order.fill(warehouse);    assertTrue(order.isFilled());    assertEquals(0, warehouse.getInventory(TALISKER));  }  public void testOrderDoesNotRemoveIfNotEnough() {    Order order = new Order(TALISKER, 51);    order.fill(warehouse);    assertFalse(order.isFilled());    assertEquals(50, warehouse.getInventory(TALISKER));  }

xUnit tests follow a typical four phase sequence: setup, exercise,verify, teardown. In this case the setup phase is done partly in thesetUp method (setting up the warehouse) and partly in the testmethod (setting up the order). The call toorder.fill is theexercise phase. This is where the object is prodded to do thething that we want to test. The assert statements are then theverification stage, checking to see if the exercised methodcarried out its task correctly. In this case there's no explicitteardown phase, the garbage collector does this for us implicitly.

During setup there are two kinds of object that we areputting together. Order is the class that we are testing, butforOrder.fill to work we also need an instance ofWarehouse. In this situation Order is the object that we arefocused on testing. Testing-oriented people like to use termslike object-under-test or system-under-test to name such athing. Either term is an ugly mouthful to say, but as it'sa widely accepted term I'll hold my nose and use it. FollowingMeszaros I'll use System Under Test, or rather the abbreviation SUT.

So for this test I need the SUT (Order) and onecollaborator (warehouse). I need the warehouse for tworeasons: one is to get the tested behavior to work at all (sinceOrder.fill calls warehouse's methods) and secondly I needit for verification (since one of the results of Order.fill is apotential change to the state of the warehouse). As we explore thistopic further you'll see there we'll make a lot of the distinctionbetween SUT and collaborators. (In the earlier version of this articleI referred to the SUT as the "primary object" and collaborators as"secondary objects")

This style of testing uses state verification:which means that we determine whether the exercised method workedcorrectly by examining the state of the SUT and its collaboratorsafter the method was exercised. As we'll see, mock objects enable adifferent approach to verification.


Tests with Mock Objects

Now I'll take the same behavior and use mock objects. For this code I'm using the jMock library for definingmocks. jMock is a java mock object library. There are other mockobject libraries out there, but this one is an up to date librarywritten by the originators of the technique, so it makes a good one tostart with.

public class OrderInteractionTester extends MockObjectTestCase {  private static String TALISKER = "Talisker";  public void testFillingRemovesInventoryIfInStock() {    //setup - data    Order order = new Order(TALISKER, 50);    Mock warehouseMock = new Mock(Warehouse.class);        //setup - expectations    warehouseMock.expects(once()).method("hasInventory")      .with(eq(TALISKER),eq(50))      .will(returnValue(true));    warehouseMock.expects(once()).method("remove")      .with(eq(TALISKER), eq(50))      .after("hasInventory");    //exercise    order.fill((Warehouse) warehouseMock.proxy());        //verify    warehouseMock.verify();    assertTrue(order.isFilled());  }  public void testFillingDoesNotRemoveIfNotEnoughInStock() {    Order order = new Order(TALISKER, 51);        Mock warehouse = mock(Warehouse.class);          warehouse.expects(once()).method("hasInventory")      .withAnyArguments()      .will(returnValue(false));    order.fill((Warehouse) warehouse.proxy());    assertFalse(order.isFilled());  }

Concentrate ontestFillingRemovesInventoryIfInStock first, as I've takena couple of shortcuts with the later test.

To begin with, the setup phase is very different. For a startit's divided into two parts: data and expectations. The datapart sets up the objects we are interested in working with, inthat sense it's similar to the traditional setup. The differenceis in the objects that are created. The SUT is the same -an order. However the collaborator isn't a warehouse object,instead it's a mock warehouse - technically an instance of theclassMock.

The second part of the setup creates expectations on the mockobject.The expectations indicate which methods should be called on themocks when the SUT is exercised.

Once all the expectations are in place I exercise theSUT. After the exercise I then do verification, which has twoaspects. I runasserts against the SUT - much as before. However I alsoverify the mocks - checking that they were called according to theirexpectations.

The key difference here is how we verify that the order didthe right thing in its interaction with the warehouse. Withstate verification we do this by asserts against the warehouse'sstate. Mocks usebehavior verification, where we instead checkto see if the order made the correct calls on the warehouse. Wedo this check by telling the mock what to expect during setupand asking the mock to verify itself during verification. Onlythe order is checked using asserts, and if the the methoddoesn't change the state of the order there's no asserts at all.

In the second test I do a couple of different things. Firstly Icreate the mock differently, using themock methodin MockObjectTestCase rather than the constructor. This is aconvenience method in the jMock library that means that I don't need toexplicitly call verify later on, any mock created with the conveniencemethod is automatically verified at the end of the test. I could havedone this in the first test too, but I wanted to show the verificationmore explicitly to show how testing with mocks works.

The second different thing in the second test case is that I'verelaxed the constraints on the expectation by usingwithAnyArguments. The reason for this is that the firsttest checks that the number is passed to the warehouse, so the secondtest need not repeat that element of the test. If the logic of theorder needs to be changed later, then only one test will fail, easingthe effort of migrating the tests. As it turns out I could have leftwithAnyArguments out entirely, as that is the default.

Using EasyMock

There are a number of mock object libraries out there. Onethat I come across a fair bit is EasyMock, both in its java and .NETversions. EasyMock also enable behavior verification, but hasa couple of differences in style with jMock which are worthdiscussing. Here are the familiar tests again:

public class OrderEasyTester extends TestCase {  private static String TALISKER = "Talisker";    private MockControl warehouseControl;  private Warehouse warehouseMock;    public void setUp() {    warehouseControl = MockControl.createControl(Warehouse.class);    warehouseMock = (Warehouse) warehouseControl.getMock();      }  public void testFillingRemovesInventoryIfInStock() {    //setup - data    Order order = new Order(TALISKER, 50);        //setup - expectations    warehouseMock.hasInventory(TALISKER, 50);    warehouseControl.setReturnValue(true);    warehouseMock.remove(TALISKER, 50);    warehouseControl.replay();    //exercise    order.fill(warehouseMock);        //verify    warehouseControl.verify();    assertTrue(order.isFilled());  }  public void testFillingDoesNotRemoveIfNotEnoughInStock() {    Order order = new Order(TALISKER, 51);        warehouseMock.hasInventory(TALISKER, 51);    warehouseControl.setReturnValue(false);    warehouseControl.replay();    order.fill((Warehouse) warehouseMock);    assertFalse(order.isFilled());    warehouseControl.verify();  }}

EasyMock uses a record/replay metaphor for settingexpectations. For each object you wish to mock you create a controland mock object. The mock satisfies the interface of the secondaryobject, the control gives you additional features. To indicate anexpectation you call the method, with the arguments you expect on themock. You follow this with a call to the control if you want a returnvalue. Once you've finished setting expectations you call replay onthe control - at which point the mock finishes the recording and isready to respond to the primary object. Once done you call verify onthe control.

It seems that while people are often fazed at first sight bythe record/replay metaphor, they quickly get used to it. It has anadvantage over the constraints of jMock in that you are makingactual method calls to the mock rather than specifying method names instrings. This means you get to use code-completion in your IDE and anyrefactoring of method names will automatically update the tests. Thedownside is that you can't have the looser constraints.

The developers of jMock are working on a new version whichwill use other techniques to allow you use actual method calls.


The Difference Between Mocks and Stubs

When they were first introduced, many people easily confusedmock objects with the common testing notion of usingstubs. Since then it seems people have better understood thedifferences (and I hope the earlier version of this paperhelped). However to fully understand the way people use mocks itis important to understand mocks and other kinds of testdoubles. ("doubles"? Don't worry if this is a new term to you,wait a few paragraphs and all will be clear.)

When you're doing testing like this, you're focusing onone element of the software at a time -hence the common term unittesting. The problem is that to make a single unit work, youoften need other units - hence the need for some kind ofwarehouse in our example.

In the two styles of testing I've shown above, the first caseuses a real warehouse object and the second case uses a mockwarehouse, which of course isn't a real warehouse object. Usingmocks is one way to not use a real warehouse in the test, butthere are other forms of unreal objects used in testing like this.

The vocabulary for talking about this soon gets messy - allsorts of words are used: stub, mock, fake, dummy. For thisarticle I'm going to follow the vocabulary of Gerard Meszaros's book. It's not what everyone uses, but I think it's agood vocabulary and since it's my essay I get to pick whichwords to use.

Meszaros uses the term Test Double as the generic term forany kind of pretend object used in place of a real object fortesting purposes. The name comes from the notion of a StuntDouble in movies. (One of his aims was to avoid using any namethat was already widely used.) Meszaros then defined fourparticular kinds of double:

  • Dummy objects are passed around but never actuallyused. Usually they are just used to fill parameter lists.
  • Fake objects actually have working implementations, butusually take some shortcut which makes them not suitable forproduction (anin memory database is a good example).
  • Stubs provide canned answers to calls made during the test,usually not responding at all to anything outside what'sprogrammed in for the test. Stubs may also record informationabout calls, such as an email gateway stub that remembers themessages it 'sent', or maybe only how many messages it 'sent'.
  • Mocks are what we are talking about here: objectspre-programmed with expectations which form a specification of thecalls they are expected to receive.

Of these kinds of doubles, only mocks insist upon behaviorverification. The other doubles can, and usually do, use stateverification. Mocks actually do behave like other doubles duringthe exercise phase, as they need to make the SUT believe it'stalking with its real collaborators - but mocks differ inthe setup and the verification phases.

To explore test doubles a bit more, we need to extend ourexample. Many people only use a test double if the real object isawkward to work with. A more common case for a test double would be ifwe said that we wanted to send an email message if we failed to fillan order. The problem is that we don't want to send actual emailmessages out to customers during testing. So instead we create a testdouble of our email system, one that we can control andmanipulate.

Here we can begin to see the difference between mocks andstubs. If we were writing a test for this mailing behavior, wemight write a simple stub like this.

public interface MailService {  public void send (Message msg);}
public class MailServiceStub implements MailService {  private List<Message> messages = new ArrayList<Message>();  public void send (Message msg) {    messages.add(msg);  }  public int numberSent() {    return messages.size();  }}                                 

We can then use state verification on the stub like this.

class OrderStateTester...

  public void testOrderSendsMailIfUnfilled() {    Order order = new Order(TALISKER, 51);    MailServiceStub mailer = new MailServiceStub();    order.setMailer(mailer);    order.fill(warehouse);    assertEquals(1, mailer.numberSent());  }

Of course this is a very simple test - only that a messagehas been sent. We've not tested it was sent to the right person,or with the right contents, but it will do to illustrate thepoint.

Using mocks this test would look quite different.

class OrderInteractionTester...

  public void testOrderSendsMailIfUnfilled() {    Order order = new Order(TALISKER, 51);    Mock warehouse = mock(Warehouse.class);    Mock mailer = mock(MailService.class);    order.setMailer((MailService) mailer.proxy());    mailer.expects(once()).method("send");    warehouse.expects(once()).method("hasInventory")      .withAnyArguments()      .will(returnValue(false));    order.fill((Warehouse) warehouse.proxy());  }}

In both cases I'm using a test double instead of the realmail service. There is a difference in that the stub usesstate verification while the mock uses behavior verification.

In order to use state verification on the stub, I need tomake some extra methods on the stub to help with verification. As aresult the stub implementsMailService but adds extratest methods.

Mock objects always use behavior verification, a stub can goeither way. Meszaros refers to stubs that use behaviorverification as a Test Spy. The difference is in how exactly thedouble runs and verifies and I'll leave that for you to exploreon your own.


Classical and Mockist Testing

Now I'm at the point where I can explore the seconddichotomy: that between classical and mockist TDD. The big issuehere iswhen to use a mock (or other double).

The classical TDD style is to use real objects ifpossible and a double if it's awkward to use the real thing. Soa classical TDDer would use a real warehouse and a double forthe mail service. The kind of double doesn't really matter thatmuch.

A mockist TDD practitioner, however, will alwaysuse a mock for any object with interesting behavior. In this case forboth the warehouse and the mail service.

Although the various mock frameworks were designed withmockist testing in mind, many classicists find them useful forcreating doubles.

An important offshoot of the mockist style is that of Behavior Driven Development (BDD). BDDwas originally developed by my colleague Dan North as a technique tobetter help people learn Test Driven Development by focusing on howTDD operates as a design technique. This led to renaming tests asbehaviors to better explore where TDD helps with thinking about whatan object needs to do. BDD takes a mockist approach, but it expands onthis, both with its naming styles, and with its desire to integrateanalysis within its technique. I won't go into this more here, as theonly relevance to this article is that BDD is another variation on TDDthat tends to use mockist testing. I'll leave it to you to follow thelink for more information.


Choosing Between the Differences

In this article I've explained a pair of differences: stateor behavior verification / classic or mockist TDD. What are thearguments to bear in mind when making the choices between them? I'llbegin with the state versus behavior verification choice.

The first thing to consider is the context. Are we thinkingabout an easy collaboration, such as order and warehouse, or anawkward one, such as order and mail service?

If it's an easycollaboration then the choice is simple. If I'm a classic TDDerI don't use a mock, stub or any kind of double. I use a realobject and state verification. If I'm a mockist TDDer I use amock and behavior verification. No decisions at all.

If it's an awkward collaboration, then there's no decision ifI'm a mockist - I just use mocks and behavior verification. IfI'm a classicist then I do have a choice, but it's not a bigdeal which one to use. Usually classicists will decide on a caseby case basis, using the easiest route for each situation.

So as we see, state versus behavior verification is mostlynot a big decision. The real issue is between classic andmockist TDD. As it turns out the characteristics of state andbehavior verification do affect that discussion, and that'swhere I'll focus most of my energy.

But before I do, let me throw in an edge case. Occasionallyyou do run into things that are really hard to use stateverification on, even if they aren't awkward collaborations. Agreat example of this is a cache. The whole point of a cache isthat you can't tell from its state whether the cache hit ormissed - this is a case where behavior verification would be thewise choice for even a hard core classical TDDer. I'm sure thereare other exceptions in both directions.

As we delve into the classic/mockist choice, there's lots offactors to consider, so I've broken them out into rough groups.

Driving TDD

Mock objects came out of the XP community, and one of theprincipal features of XP is its emphasis on Test Driven Development -where a system design is evolved through iteration driven by writingtests.

Thus it's no surprise that the mockistsparticularly talk about the effect of mockist testing on a design. Inparticular they advocate a style called need-driven development.With this style you begin developing auser story by writing your firsttest for the outside of your system, making some interface object yourSUT. By thinking through the expectations uponthe collaborators, you explore the interaction between the SUT and its neighbors - effectively designing the outboundinterface of the SUT.

Once you have your first testrunning, the expectations on the mocks provide a specification forthe next step and a starting point for the tests. You turn eachexpectation into a test on a collaborator and repeat the processworking your way into the system one SUT at a time. This style is alsoreferred to as outside-in, which is a very descriptive name for it. Itworks well with layered systems. You first start by programming the UI usingmock layers underneath. Then you write tests for the lower layer,gradually stepping through the system one layer at a time. This is avery structured and controlled approach, one that many people believeis helpful to guide newcomers to OO and TDD.

Classic TDD doesn't provide quite the same guidance. You can do a similar stepping approach, using stubbed methods instead of mocks. To do this, whenever you need something from a collaborator you just hard-code exactly the response the test requires to make the SUT work. Then once you're green with that you replace the hard coded response with a proper code.

But classic TDD can do other things too. A common style ismiddle-out. In this style you take a feature and decide what you needin the domain for this feature to work. You get the domain objects todo what you need and once they are working you layer the UI on top.Doing this you might never need to fake anything. A lot of people likethis because it focuses attention on the domain model first, whichhelps keep domain logic from leaking into the UI.

I should stress that both mockists and classicists do thisone story at a time. There is a school of thought that buildsapplications layer by layer, not starting one layer until another iscomplete. Both classicists and mockists tend to have an agilebackground and prefer fine-grained iterations. As a result they workfeature by feature rather than layer by layer.

Fixture Setup

With classic TDD, you have to create not just the SUT butalso all the collaborators that the SUT needs in response to thetest. While theexample only had a couple of objects, real tests often involve a largeamount of secondary objects. Usually these objects are created andtorn down with each run of the tests.

Mockist tests, however, only need to create theSUT and mocks for its immediate neighbors. This can avoidsome of the involved work in building up complex fixtures (At least intheory. I've come across tales of pretty complex mock setups, but thatmay be due to not using the tools well.)

In practice, classic testers tend to reuse complexfixtures as much as possible. In the simplest way you do this byputting fixture setup code into the xUnit setup method. Morecomplicated fixtures need to be used by several test classes, so inthis case you create special fixture generation classes. I usually callthese Object Mothers, based on a naming convention used on an earlyThoughtWorks XP project. Using mothers is essential in larger classic testing, but the mothers are additional code that need to bemaintained and any changes to the mothers can have significant rippleeffects through the tests. There also may be a performance cost insetting up the fixture - although I haven't heard this to be a seriousproblem when done properly. Most fixture objects are cheap to create,those that aren't are usually doubled.

As a result I've heard both styles accuse the other of beingtoo much work. Mockists say that creating the fixtures is alot of effort, but classicists say that this is reused but you haveto create mocks with every test.

Test Isolation

If you introduce a bug to a system with mockist testing, itwill usually cause only tests whose SUT contains the bug to fail. Withthe classic approach, however, any tests of client objects can alsofail, which leads to failures where the buggy object is used as acollaborator in another object's test. As a result a failure in ahighly used object causes a ripple of failing tests all across thesystem.

Mockist testers consider this to be a major issue; it results in a lot of debugging in order to findthe root of the error and fix it. However classicists don'texpress this as a source of problems. Usually the culprit isrelatively easy to spot by looking at which tests fail and thedevelopers can tell that other failures are derived from the rootfault. Furthermore if you are testing regularly (as you should) thenyou know the breakage was caused by what you last edited, so it's notdifficult to find the fault.

One factor that may be significant here is the granularity ofthe tests. Since classic tests exercise multiple real objects,you often find a single test as the primary test fora cluster of objects, rather than just one. If that clusterspans many objects, then it can be much harder to find the realsource of a bug. What's happening here is that the tests are toocoarse grained.

It's quite likely that mockist tests are less likely tosuffer from this problem, because the convention is to mock out allobjects beyond the primary, which makes it clear that finer grainedtests are needed for collaborators. That said, it's also true thatusing overly coarse grained tests isn't necessarily a failure of classictesting as a technique, rather a failure to do classic testingproperly. A good rule of thumb is to ensure that you separatefine-grained tests for every class. While clusters are sometimesreasonable, they should be limited to only very few objects - no morethan half a dozen. In addition, if you find yourself with a debuggingproblem due to overly coarse-grained tests, you should debug in a testdriven way, creating finer grained tests as you go.

In essence classic xunit tests are not just unit tests, butalso mini-integration tests. As a result many people like thefact that client tests may catch errors that the main tests foran object may have missed, particularly probing areas whereclasses interact. Mockist tests lose thatquality. In addition you also run the risk that expectations onmockist tests can be incorrect, resulting in unittests that run green but mask inherent errors.

It's at this point that I should stress that whichever styleof test you use, you must combine it with coarser grained acceptancetests that operate across the system as a whole. I've often comeacross projects which were late in using acceptance tests andregretted it.

Coupling Tests to Implementations

When you write a mockist test, you are testing theoutbound calls of the SUT to ensure it talks properly toits suppliers. A classic test only cares about the final state -not how that state was derived. Mockist tests are thus morecoupled to the implementation of a method. Changing the nature ofcalls to collaborators usually cause a mockist test tobreak.

This coupling leads to a couple of concerns. The mostimportant one is the effect on Test Driven Development. Withmockist testing, writing the test makes you think about theimplementation of the behavior - indeed mockist testers see thisas an advantage. Classicists, however, think that it's importantto only think about what happens from the external interface and toleave all consideration of implementation until after you're donewriting the test.

Coupling to the implementation also interferes withrefactoring, since implementation changes are much more likely tobreak tests than with classic testing.

This can be worsened by the nature of mocktoolkits. Often mock tools specify very specific method calls andparameter matches, even when they aren't relevant to this particulartest. One of the aims of the jMock toolkit is to be more flexible inits specification of the expectations to allow expectations to belooser in areas where it doesn't matter, at the cost of using stringsthat can make refactoring more tricky.

Design Style

One of the most fascinating aspects of these testingstyles to me is how they affect design decisions. As I've talked withboth types of tester I've become aware of a few differences betweenthe designs that the styles encourage, but I'm sure I'm barelyscratching the surface.

I've already mentioned a difference in tackling layers.Mockist testing supports an outside-in approach while developers whoprefer a domain model out style tend to prefer classic testing.

On a smaller level I noticed that mockist testerstend to ease away from methods that return values, in favor of methodsthat act upon a collecting object. Take the example of the behavior ofgathering information from a group of objects to create a reportstring. A common way to do this is to have the reporting method callstring returning methods on the various objects and assemble theresulting string in a temporary variable. A mockist testerwould be more likely to pass a string buffer into the various objectsand get them to add the various strings to the buffer - treating thestring buffer as a collecting parameter.

Mockist testers do talk more about avoiding 'trainwrecks' - method chains of style ofgetThis().getThat().getTheOther(). Avoiding method chainsis also known as following the Law of Demeter. While method chainsare a smell, the opposite problem of middle men objects bloated withforwarding methods is also a smell. (I've always felt I'd be morecomfortable with the Law of Demeter if it were called the Suggestionof Demeter.)

One of the hardest things for people to understand in OOdesign is the "Tell Don't Ask"principle, which encourages you to tell an object to dosomething rather than rip data out of an object to do it inclient code. Mockists say that using mockist testing helpspromote this and avoid the getter confetti that pervades toomuch of code these days. Classicists argue that there are plentyof other ways to do this.

An acknowledged issue with state-basedverification is that it can lead to creating query methods only to supportverification. It's never comfortable to add methods to the API of anobject purely for testing, using behavior verification avoids thatproblem. The counter-argument to this is that such modifications areusually minor in practice.

Mockists favor role interfaces and assert that usingthis style of testing encourages more role interfaces, sinceeach collaboration is mocked separately and is thus more likelyto be turned into a role interface. So in my example aboveusing a string buffer for generating a report, a mockist wouldbe more likely to invent a particular role that makes sense inthat domain, whichmay be implemented by a string buffer.

It's important to remember that this difference in designstyle is a key motivator for most mockists. TDD's origins were adesire to get strong automatic regressiontesting that supported evolutionary design. Along the way itspractitioners discovered that writing tests first made asignificant improvement to the design process. Mockists have astrong idea of what kind of design is a good design and havedeveloped mock libraries primarily to help people develop thisdesign style.


So should I be a classicist or a mockist?

I find this a difficult question to answer with confidence.Personally I've always been a old fashioned classic TDDer and thus farI don't see any reason to change. I don't see any compelling benefitsfor mockist TDD, and am concerned about the consequences of couplingtests to implementation.

This has particularly struck me when I've observed a mockistprogrammer. I really like the fact that while writing the testyou focus on the result of the behavior, not how it's done. Amockist is constantly thinking about how the SUT is going to beimplemented in order to write the expectations. This feelsreally unnatural to me.

I also suffer from the disadvantage of not trying mockist TDDon anything more than toys. As I've learned from Test DrivenDevelopment itself, it's often hard to judge a technique withouttrying it seriously. I do know many good developers who are very happyand convinced mockists. So although I'm still a convinced classicist,I'd rather present both arguments as fairly as I can so you can makeyour own mind up.

So if mockist testing sounds appealing to you, I'dsuggest giving it a try. It's particularly worth trying if you arehaving problems in some of the areas that mockist TDD isintended to improve. I see two main areas here. One is if you'respending a lot of time debugging when tests fail because they aren'tbreaking cleanly and telling you where the problem is. (You could alsoimprove this by using classic TDD on finer-grainedclusters.) The second area is if your objects don't contain enoughbehavior, mockist testing may encourage the development team tocreate more behavior rich objects.


Final Thoughts

As interest in unit testing, the xunit frameworks and TestDriven Development has grown, more and more people are runninginto mock objects. A lot of the time people learn a bit aboutthe mock object frameworks, without fully understanding themockist/classical divide that underpins them. Whichever side ofthat divide you lean on, I think it's useful to understand thisdifference in views. While you don't have to be a mockist tofind the mock frameworks handy, it is useful to understand thethinking that guides many of the design decisions of the software.

The purpose of this article was, and is, to point out thesedifferences and to lay out the trade-offs between them. There ismore to mockist thinking than I've had time to go into,particularly its consequences on design style. I hope that inthe next few years we'll see more written on this and that willdeepen our understanding of the fascinating consequences ofwriting tests before the code.


Share:

For articles on similar topics…

…take a look at the following tags:

populartesting


Further Reading

For a thorough overview of xunit testing practice, keep aneye out for Gerard Meszaros's forthcoming book (disclaimer: it's in myseries). He also maintains awebsite with the patterns from the book.

To find out more about TDD, the first place to look is Kent'sbook.

To find out more about the mockist style of testing, the best overall resource isFreeman & Pryce. The authors look after mockobjects.com. In particular read the excellent OOPSLApaper. For more on Behavior Driven Development, a differentoffshoot of TDD that is very mockist in style, start with Dan North'sintroduction.

You can also find out more about thesetechniques by looking at the tool websites forjMock, nMock, EasyMock, and the.NET EasyMock.(There are other mock tools out there, don't consider this list to becomplete.)

XP2000 saw the original mockobjects paper, but it's rather outdated now.

Significant Revisions

02 January 2007: Split the original distinction ofstate-based versus interaction-based testing into two: state versusbehavior verification and classic versus mockist TDD. I also madevarious vocabulary changes to bring it into line with GerardMeszaros's book of xunit patterns.

08 July 2004: First published

0 0