views:

79

answers:

3

I'm still in the learning stages regarding unit-testing and in particular regarding mocking (I'm using the PascalMock and DUnit frameworks). One thing I now stumbled over was that I couldn't find a way around hard-coding implementation details of the tested class/interface into my unit test and that just feels wrong...

For example: I want to test a class that implements a very simple interface for reading and writing application settings (basically name/value pairs). The interface that is presented to the consumer is completely agnostic to where and how the values are actually stored (e.g. registry, INI-file, XML, database, etc.). Naturally, the access layer is implemented by yet a different class that gets injected into the tested class on construction. I created a mock object for this access layer and I am now able to fully test the interface-implementing class without actually reading or writing anything to any registry/INI-file/whatever.

However, in order to ensure the mock behaves exactly like the real thing when accessed by the tested class, my unit tests have to set up the mock object by very explicitly defining expected method calls and the return values expected by the tested class. This means that if I should ever have to make changes to the interface of the access layer or to the way that the tested class uses that layer I will also have to change the unit tests for the class that internally uses that interface even though the interface of the class I'm actually testing hasn't changed at all. Is this something I will just have to live with when using mocks or is there a better way to design the class-dependencies that would avoid this?

+3  A: 

to ensure the mock behaves exactly like the real thing when accessed by the tested class, my unit tests have to set up the mock object by very explicitly defining expected method calls and the return values expected by the tested class.

Correct.

changes to the interface of the access layer or to the way that the tested class uses that layer I will also have to change the unit tests

Correct.

even though the interface of the class I'm actually testing hasn't changed at all.

"Actually testing"? You mean the exposed interface class? That's fine.

The way the "tested" (interface) class uses the access layer means you've changed the internal interface to the access layer. Interface changes (even internal ones) require test changes and may lead to breakage if you've done something wrong.

Nothing wrong with this. Indeed, the whole point is that any change to the access layer must require changes to the mocks to assure that the change "works".

Testing is not supposed to be "robust". It's supposed to be brittle. If you make a change that alters internal behavior, then things can break. If your tests were too robust they wouldn't test anything -- they'd just work. And that's wrong.

Tests should only work for the exact right reason.

S.Lott
Thanks, that sounds like a very good line of reasoning. However, I had been under the impression that unit tests are supposed to treat their subjects (i.e. the tested classes and methods) more like black boxes, concentrating fully on *what* they do rather than *how* they do it. Yet in this case I am basically forced to duplicate almost the entire implementation of the tested methods in the form of mock expectations... Isn't that quite contrary to the black-box principle? Or did I get that completely wrong?
Oliver Giesen
`It's supposed to be brittle`? Not sure if I agree on that.
Lieven
I encourage OP to look on further info on Classicist vs. Mockist Test-Driven Development here <http://codebetter.com/blogs/ian_cooper/archive/2008/02/04/classicist-vs-mockist-test-driven-development.aspx>, and especially here <http://stackoverflow.com/questions/184666/should-i-practice-mockist-or-classical-tdd>. Not necesarly contradicting what you say but it gives some other points of view.
Lieven
"Isn't that quite contrary to the black-box principle". No. You happen to know **too much** of how the thing is implemented. Since you know **too much** of the implementation, you worry about the duplication. Since you know too much, you can duplicate when writing the Mock. If you brought in an innocent n00b programmer, they may write a simpler Mock. Or, they may do what you did and write a Mock that has a similar structure to the real class. It doesn't matter. You worry because you know **too much** about Mock and implementation.
S.Lott
@Lieven : Thanks for the links! I wasn't yet aware of that argument. Very interesting reads so far.
Oliver Giesen
@S.Lott : It's not exactly that I just "happen to know too much of how the thing is implemented". What I'm worrying about is more the fact that I **have to*** know all that in order to set up the test in the first place. A noob that doesn't know how the tested class uses the access layer internally will simply not be able to write a test using a mock access layer at all...
Oliver Giesen
@Oliver Giesen: The n00b needs a minimal interface specification to create the Mock. Which they would do, quite nicely. When you looked at their Mock, you might say "That's a lot like the actual implementation." Only you can make the judgement, not the n00b. The n00b created the Mock, purely from interface specifications. Perhaps your interface is 'too low level' and forces a specific implementation. Your approach is absolutely right. Your worry about your Mocks being too much like the implementation is misplaced.
S.Lott
Re: the brittle tests issue: Tests don't need to be brittle. They can be written generically to allow a little flexibility when testing, but from a test-driven perspective this would likely devalue the tests themselves. Changes to tested code should break tests, and is one of the principles underpinning sound refactoring methodology, yet doesn't always mean correct testing strategy has been applied. If you are writing well-factored tests, they will be brittle sometimes (eg: interfaces), flexible other times (eg: some behaviours), so the 'argument' isn't really as polarised as it may seem.
S.Robins
@S.Robins: "Changes to tested code should break tests." Agreed. "the 'argument' isn't really as polarised". Probably true. Though this question might indicate that it's polarizing for some people. The problem arises when we allow a developer to 'over-engineer' tests to make them robust. As you said, it's a fine line, and I lobby for always coming down on the brittle side. A broken test worked -- it detected a change.
S.Lott
@S.Lott: Yes, the fine line between over-engineered vs appropriate is a key issue, and likely where polarised views are rooted. I agree that a broken test works if it detects change, but again this needs to be balanced, and only experience can tell you where to draw the line... and even then you might not get it "right". Actually I think that the last line in your post sums it up best (and I will probably be quoting you on that). "Tests should only work for the exact right reason". If we treat that as a golden rule, then how brittle or robust the test is moot.
S.Robins
+3  A: 

Is this something I will just have to live with when using mocks or is there a better way to design the class-dependencies that would avoid this?

A lot of times mocks (particularly sensitive frameworks like JMock) force you to account for details that don't relate directly to the behavior you're trying to test, and sometimes this can even be helpful by exposing suspect code that is doing too much and has too many calls/dependencies.

However in your case, if I read your description right, it sounds like you really don't have a problem. If you design the read/write layer correctly and with an appropriate level of abstraction, you shouldn't have to change it.

This means that if I should ever have to make changes to the interface of the access layer or to the way that the tested class uses that layer I will also have to change the unit tests for the class that internally uses that interface even though the interface of the class I'm actually testing hasn't changed at all.

Isn't the point of writing the abstracted access layer to avoid this? In general, following the Open/Closed principle, an interface of this sort shouldn't change and shouldn't break the contract with the class that consumes it, and by extension it won't break your unit tests either. Now if you change the order of the method calls, or have to make new calls to the abstracted layer, then, yes, particularly with some frameworks, your mock expectations will break. This is just part of the cost of using mocks, and it's perfectly acceptable. But the interface itself should, in general, remain stable.

Dave Sims
Very good point! Thanks!
Oliver Giesen
The biggest problem with using PascalMock (as compared to some of the other more robust frameworks) is that you need to manually define the the mocks as complete classes. It feels like a big time waster, but in general you only do this once to get a nice and powerful addition to your testing toolkit. To paraphrase Dave a little, having a well defined abstraction layer helps to reduce the pain somewhat, and provides you with a solution that allows you to ignore the inner workings of the classes that aren't being tested. The trade off is that it's brittle if interfaces need to be altered.
S.Robins
+1  A: 

Just to put some names to your example,

  • RegistryBasedDictionary implements the Role (interface) Dictionary.
  • RegistryBasedDictionary has a dependency on the Role RegistryAccessor, implemented by RegistryWinAPIWrapper.

You are currently interested in testing RegistryBasedDictionary. The unit tests would inject a mock dependency for the RegistryAccessor Role and would test the expected interaction with the dependencies.

  • The trick here to avoid unnecessary test-maintenance is to "Specify precisely what should happen.. and no more." (From the GOOS book (must-read for mock flavored TDD), so if order of dependency method calls doesn't matter, don't specify it in the test. That leaves you free to change the order of calls in the implementation.)
  • Design the Roles such that the they do not contain any leaks from the actual implementations - keep the Roles implementation-agnostic.

The only reason to change RegistryBasedDictionary tests, would be a change in the behavior of RegistryBasedDictionary and not in any of its dependencies. So if its interaction with its dependencies or the roles/contracts change, the tests would need to be updated. That is the price of interaction-based tests you need to pay, for the flexibility to test in isolation. However in practice, it doesn't that happen that often.

Gishu