Over Mocking
I have noticed using more mocks lately. Instead of using them sparingly for controlling services which are not instrumental to what I am testing, I am essentially using them as a way to isolated what I am testing. This has me noticing a side effect I am not entirely comfortable with. To use the mock, I need to be able to have the mock in the production code, which means I need to inject an abstraction of the mock (dynamic mocks will do that for you).
So, the side effect is long parameter lists, specifically in constructors. Everything takes almost all the objects it uses. Then, to create the production objects are factories. I am using many more factories than I would without mass mocking. I am not sure if this is a good use of dependency decoupling and factory building as design techniques, or if this is a symptom of over design and tightly coupling the mocks to the tests, making it more difficult to change the tests without changing the interface the mock is implementing. Thoughts?
Wed, 24 May 2006 14:03:11, Dave Hoover, Create a default constructor Try poor man’s dependency injection: have a constructor that allows you to inject your dependencies (hidden behind interfaces) for testing, then have a default constructor that instantiates the concrete objects and injects them itself.
Wed, 24 May 2006 15:01:02, David Chelimsky, too many dependencies either way I don’t think mocks or factories are the real problem here. If A depends on the services of B,C,D,E,F,G,H and I, then it’s depending on too many things whether you inject the objects or instantiate them internally.
Wed, 24 May 2006 16:57:06, , Don’t “inject” dependencies. Mock an object’s peers in tests and pass real peers to it’s constructor or as method arguments. Composite objects should create objects they use internally and not expose them at the constructor. A composite object should be simpler than the sum of its parts. Also, avoid passing all dependencies into an object’s constructor: you can probably pass collaborators as method arguments, not constructor arguments.
Wed, 24 May 2006 17:24:56, Dave Hoover, Listen to Chelimsky The smell of a long parameter list leads me to think you may be able to collapse some objects behaind a coarser-grained interface.
Thu, 25 May 2006 11:24:12, Ryan Cooper, DI Containers I find using a DI container like PicoContainer eases the pain by making all (or most of) those factories unnecessary.
I’m still not happy about the long parameter lists in constructors, so I think I will give a few of the suggestions above a try. Thanks all.
Thu, 25 May 2006 16:08:05, Tim Ottinger, The “Airplane” retort “I’ll never be over mocking!”
Fri, 26 May 2006 10:48:42, Asher Sterkin, Over Mocking Personally I mock whatever and whenever I can. Usually a hardship with mocking reveals some interesting deeper design problems. If there are too many arguments for constructor it probably means the class under test has too many dependencies, which means a problem with cohesion. In my experience I seldom need to mock more than 5 interfaces per test case. It’s usually one or two as constructor arguments and two, max three for returned objects. If it’s more I try to re-factor.
Fri, 26 May 2006 16:25:43, Paul Pagel, “In my experience I seldom need to mock more than 5 interfaces per test case.” Do you only mock interfaces?
Sat, 27 May 2006 19:49:22, Rogerio Liesenfeld, I prefer to obtain dependencies with “new” wherever possible In the large (8000+ classes) J2EE web app I am currently working, many business classes obtain instances of other business classes by simply instantiating them with the “new” operator. Besides being the simplest way to obtain dependencies, this allows business service classes to maintain client-specific state, so that private methods don’t need to have any parameters.
Of course, with the mock objects tools previously available, it would have been difficult to unit test such classes. But I wanted to use “new”, and also have final classes, final methods, and static methods where appropriate. So, with that “itch to scratch”, I went ahead and created a new tool that would free me to design the application without constraints, and still have complete unit tests.
If anyone wants to give it a try, the tool is here: jmockit.dev.java.net.
Fri, 9 Jun 2006 10:30:20, Asher Sterkin, Over Mocking “Do you only mock inerfaces?” Not only, but I prefer so. In C++ it really doesn’t matter since there is no good mock library (I use my own which is based on link time substitution; I plan to sourceforge it soon). In Java and C# interfaces do not cost too much, but sometimes you have a class comming from the infrastrucure. You would like to mock it but you do not want to wrap it with an extra interface. For example I recently mocked Java’s TimerTask::cancel in order to validate that in a certain scenario a timer cancellation request is sent properly. I also do test what will my program do if the TimerTask::cancel returns false (too late to cancel).
Fresh Testing
Sometimes I sit down to write a test on something I haven’t worked on before or don’t know intimately, and I just can’t write the first test. I need a context of the system and a state of existing code. Then I generally do one of two things: look at how things are implemented in the code, or look at other tests that exercise similar behavior. Using either of these as a strict model to write a test is problematic to the flow of TDD.
By using the implementation code as a model, I am limiting one of the great things about TDD from the beginning, the fact the design should evolve as a byproduct of making the test pass. This TDD complacent method tends to ingrain existent design into my mind. These strict models resign to existing design, even if your story/test/problem isn’t exactly the same, just similar enough to convince you of the model.
Copy/pasting a similar test and editing is a developer mistake I commit sometimes when it looks like a freebie is being tossed at me: duplicate then abstract. It is very tempting, yet I have found it painfully regressive. Especially when the tests themselves have begun to rot, as their ability to act as developer docs are deprecated. It causes a lie in logic which is always painful. Either the debugger gets fired up or the test gets scrapped in order to handwrite anyway. Copy/pasting something which is similar is starting from a false expectation most of the time. It is more important to me to have faith in the integrity of my tests.
The most powerful TDD I see is at the beginning of a project, since there is a state of tabula rasa allowing you to move infinitely lateral. Before tests depreciates provides the best model for TDD. Test depreciation is unavoidable, as with design changes, the tests are changed with regularity to accommodate the new structures. The reason for having the tests is to have a safety net to make the code easy to change. It becomes important to keep tests “fresh” when there is already design in place. Otherwise good design appears to degrade due to over/improper use.
Handwriting a test from scratch can seem like an extra step, like reinventing the wheel. Most of the time the extra step is exactly that, an extra step, but in those cases where you are following a false model it is very expensive. It introduces the worst type of design into the system, the kind with little to no smell, but with false premises. Introducing bad design into tests or failing to maintain test code ends up introducing bad design into production code.
COMENTS
Thu, 5 Jan 2006 22:16:48, David Chelimsky, copy/paste Copy/paste can be a real problem, yet it takes an awful lot of discipline to abandon it completely. For example if you’re test driving something new - you write the first test which has some setup in it. You’re not ready to move stuff to a setup method because there’s no duplication to warrant it yet. So for the second test you copy/paste the first test, modify what you will, get it to pass, and then refactor out the duplication. With just a few lines of testing code that’s probably acceptable, but there’s a point where you get yourself in trouble. I guess that’s different for everyone, and perhaps on different days (depending on the coffee/sleep ratio). Maybe committing to a day of absolutely zero copy/paste to see if it really slows me down would be worthwhile.
Thu, 5 Jan 2006 22:53:53, Tim Ottinger, What he said I hate copying from any existing tests, always feeling like I’ve cheated somehow. It is a way to get by (or try to get by) when you don’t really know what you’re doing. When I talk about whether I [[really know][http://tottinge.blogsome.com/2005/12/30/how-can-you-not-know-java/]] something or not, I consider whether I can work from the blank slate. If not, then I don’t really know my topic. Not only is working from tabula rasa liberating, it’s a good indicator that you’ve done enough backgrounding. It’s good on so many levels.
I just wrote the other side of this blog, that [[naive tests don’t help][.ArticleS.TimOttinger.NaiveTestsDontHelp]] so you have to know the software to write tests for new parts. I think that it would be good if we could work on the pro/con of this and give guidance on navigating between sylla and charybdis. If you don’t know enough, you can’t blue-sky the tests. If you are copying the tests, then you only reinforce the implementation in testing (for better or worse). There has got to be a middle way, and some heuristics to help get around: something more useful than “pairing should fix that.”
Fri, 6 Jan 2006 15:50:00, Thomas Eyde, Copy/paste is for professionals – don’t do that at home I think the trick is to write the tests for the truly new requirements, then find reusable code if any, or refactor existing code to be reusable. Not easy, but that’s what I strive for.
I also think there’s nothing wrong to copy/paste and then refactor. It’s without the refactoring things start to get dangerous.
Sat, 7 Jan 2006 12:28:24, Matisse Enzer, Copy and paste as an iterative action Yesterday I was adding tests to some “legacy” code. After I got the library to compile under the test harness I picked one subroutine to test, and create d a test to run that subroutine. Of course I got a run-time exception because the subroutine I was testing made a call to a subroutines defined in some other library. So, I created a stub version of that subroutine in my test harness. ran the test again, another missing external subroutine. I copied and pasted the stub I had created, changed the name, reran the test. Several times. Each time adding a single “fake” subroutine. (Sometimes I had to edit the stubs to return some mock data.)
I also copied-and-pasted my first test. Renamed it, and changed the arguments it passes to the subroutine I’m testing. Did this three times.
Mon, 30 Jan 2006 22:46:53, Paul Pagel, the copy/paste problem There are many ways to introduce code debt into a system. Duplicate code is one of the worse. Copy/Pasting leaves the payment of this debt to the memory of the developer. So if it is copy/paste a few lines at a time, then refactor at the end of small cycles, then I go for it. But if it is going to be coping a file or a series of tests/methods, I won’t.
Beware the Freebie
The milestones in the life of a story:
- Creation: customer creates the story
- Estimation: the story is estimated by developers
- Selection: the story is selected for an iteration
- Specification: tests are written the story
- Implementation: the story is implemented to make the tests pass
- Acceptance: the customer acknowledges completion of the story after seeing the passing tests and witnessing the new functionality
It is unavoidable. Every story must go through these milestones of life in order. A story cannot be estimated if it has not been created. A story cannot be selected for an iteration if it has not first been estimated. You can attempt to specify a story before it has been selected but you run the risk of being wrong, for the system may change many times before the story is selected. Implementing a story before it has been specified will lead to false implementations. And accepting a story that hasn’t been implemented is just plain silly.
It happens with some frequency, however, that a story gets implemented before it is ever selected, estimated, or perhaps even created. How? A developer may be look at a piece of code and think, “Hey, the customer mentioned he wanted to change this doohickey. I might as well change it now.”. Or a developer may working on one feature and say, “You know, I bet the customer would really like it if I improved this thing-a-magigger”. When this happens, the developer pridefully demonstrates the extra functionality to the customer and says with a smile, “You got a Freebie this iteration!”.
BEWARE THE FREEBIE! A Freebie, by it’s nature, has skipped at least one milestone in it’s life. Most Freebies skip the Selection milestone. This is unfortunate because maybe the customer didn’t really want that story after all. But that’s small potatoes compared to the real danger… Almost all Freebies have skipped the Specification milestone.
Skipping the specification milestone is blasphemous. If a feature has not been specified with tests, how do you know it really works? Worse, how will you know if it still works 2 months from now. Very often Freebies break and nobody knows about it.
I’ve seen it over and over and it’s regrettable every time. A perfectly innocent story is wronged by an overzealous developer. The poor story, out of sync from his peers, strays from the trodden path and becomes a Ghost Story. Becoming a Ghost Story is dreadful fate. They are those stories that were at one time complete, but have since become incomplete. You may never actually see a ghost story but they will surely haunt you. They cause users to have delusions of non-existant functionality and to make exclamations like “Hey! Why doesn’t this stupid app work like it used to?” or “What happened to my favorite feature?”. They keep customers up at night wondering if they actually selected the Ghost Story for an iteration of not. They torment developers forcing them to write code they’ve already written.
Avoid Ghost Stories by avoiding Freebies. Give every story a complete life with all it’s milestones. The next time a developers shows you extra functionality and calls it a Freebie, say “That’s nice. I’ll consider selecting a story for that in a future iteration.”
Mon, 24 Apr 2006 23:17:57, Old Grouch, Another hidded danger By skipping several stages, the freebie runs the risk of breaking something else. An assumption that doesn’t carry throughout the entire system. Usually minor, so it doesn’t show up for months. Then someone else has to rip their hair out. And declare that your parents were never married.
“The more innocuous the change, the greater the ramification.”
Tue, 21 Feb 2006 14:11:34, Gary Dieckman, Looking for Preston and Karen Martin Micah Martin - If your parents are Preston (Tom) and Karen Martin of Proctorville, Ohio, then your dad was best man at my wedding in 1970. I’d like to get in touch. Are you that Micah? gary@exprint.com
Nope, you’ve got the wrong Micah
Tue, 21 Feb 2006 14:11:34, Gary Dieckman, Looking for Preston and Karen Martin Micah Martin - If your parents are Preston (Tom) and Karen Martin of Proctorville, Ohio, then your dad was best man at my wedding in 1970. I’d like to get in touch. Are you that Micah? gary@exprint.com
Thu, 16 Feb 2006 10:09:50, unclebob, Other out of order disfunctions. Indeed! I think you have hit on an interesting way to describe a whole family of dysfunctions. I have certainly seen stories selected before they are estimated. I have seen stories accepted before they are specified. I have seen stories implemented before they are created. Each of these dysfunctions has it’s own particular set of symtoms and ramifications. What are they?
Thu, 16 Feb 2006 08:12:26, chelimsky, skipping selection Well said, Micah. It’s worth noting that 4 of the 6 milestones are customer milestones. As developers, we want to create the best software we create. We talk about it being a team effort (i.e. whole team, customers, developers, et al), but in the end it’s not really our (developers) software. And how can we expect our customer to feel ownership of priorities if we arbitrarily take the their any (sometimes all) of their milestones?