There's an interesting exchange going on in the .Net world (actually, in the Israeli .Net TDD with Mocks world, which I imagine is about as small as a community gets).
First, Roy Osherove wrote that it's occasionally worth breaking OO design principles to make code testable. Then Eli Lopian, supporter of TypeMock, responded by writing that this is a Bad Idea ™.
I'm with Osherove on this one. Although some of the rules he's worried about breaking derive from working with legacy languages, rather than one that really is object-oriented, he makes a fundamental point about Test Driven Development. The effort of adjusting your ideas to make them testable will give you better, more flexible code. This allows you to keep up with the changes that arise in any project and is the other half of the contract you sign when you choose incremental development. I'm sure that Lopian feels that his approach is the way to get there.
I'm not a fan of TypeMock, except where you're trying to crack open a legacy codebase. It's a very smart library but, to me, it misses the point of TDD with Mocks, which is to force the programmer to think about the relationships between the objects they're programming. When you override a feature in a class for the purposes of mocking (or, more often, stubbing), you're addressing a dependency between two objects, but you're leaving it implicit. When I come back to the production class, there's nothing there to tell me that it plays this particular role, so I'll have to do the analysis again. That's why I've never been keen on the cglib versions of the java mock libraries. If it's that simple an object and you don't want to introduce an interface, then don't mock, use the real thing.
But, if you have to mock out an object to get the test to work, then that object is an external dependency and should be passed in. Reaching in to an object and manipulating its internals ties the test to the implementation and makes it brittle—which is the usual objection made against interaction testing. In this case, I have to agree.
One more point, Lopian writes,
Have you ever tried to browse a code with loads of interfaces, it take ages because you have to keep finding the concrete implementation, and the place where the instance was created.In my world that's not the case. First, the usual Java IDEs take me very quickly from interface to implementation. Second, in a codebase that uses Dependency Injection well, significant objects are instantiated in just a few places. The place where the instance is created usually turns out also to be where other objects relevant to my task are also created. Then I know that the design is working.
Correction. Oren Eini is the author of Rhino Mocks,
8 comments:
Thanks for the support, but one small clarification: I'm not the developer of RhinoMocks - that would be Oren Eini (Ayende.com).
Cheers!
Roy Osherove
www.ISerializable.com
Oops. Apologies and corrections.
I guess I should also say that I was just teasing about the "legacy languages" bit. My favourite languages are older still.
This is atopic I find fascinating. I've kind of given up on designing for testability as I found it complicated my designs in ways that weren't helpful.
So although I agree with this...
Reaching in to an object and manipulating its internals ties the test to the implementation and makes it brittle—which is the usual objection made against interaction testing
..I have also found that TypeMock is useful. We quite often want to mock out domain classes (e.g. Customer/Order). I don't want to implement interfaces or include virtual methods in them just to allow mocking and in those cases I find TypeMock really helps.
Thanks for commenting. I'd be curious to see what your domain objects look like, whether it would be simpler just to use the real thing. It would be great if you could send me or post some examples.
On the counter side, I've been working with a team who have some reasonably convoluted domain code. We could have shortcut the process with something like TypeMock, but (to me) the result of breaking things up with interfaces has produced a much clearer design.
It looks like we really needed an extended bake-out, rather than both sides asserting their experience :)
exposing the implementation internals of a class to consumers is BROKEN OOD.
I don't care that the reason is to enable "testability."
Simple as that.
As we keep saying, we don't expose internals, we make dependencies explicit.We also find that taking testability into account pushes us towards what we think (IOHO) are better OO designs. Your mileage (whoever you are) may vary.
Dependency Injection means that you instansiate the object in the calling code and pass it to the code that needs it. This means two things..
1) The calling code needs to know how to instanciate the dependancy and this may involve intricate business logic.
2) The called code cannot make decisions about what object to use as it's simply passed in.
An example is a Biz object calling a DAO. The DAO will most likley create a connection object and a command object. The specifics of these objects may be based on the availability of a persistent storage device (if the database is not available use the local file system). This decision affects both the command and the connection object. With the DI pattern, all four objects will need to be created and passed in whether they are needed or not!
It is simply not correct to have to instansite all these objects simply to be able to prevent the called code from accessing the physical layer below when all we need to do is ensure that the method is called.
If you want to be forced to write good code then use FX Cop - that's what it's for. A Mocking Framework is designed to test your code not force you to write somebody elses code.
@Anonymous
I think you're missing the point here. With DI, the intention is that the called code should not be making decisions about the implementation of significant neighbouring objects. It should focus on doing one thing well, otherwise it starts to cross domains and loses coherence. I've seen so much lucky-dip code like this that has to be taken apart to be reused.
In your example, the Business Object should just receive an appropriate DAO. Any details about file systems or databases are not a matter of the logic of the business.
It looks like you've been following the material from TypeMock. We simply disagree with their view for new code. We've been doing this for many years now, generally with good results.
Next time, please consider signing your posting.
Post a Comment