08 March 2008
Another round in the testability debate
This time a posting from Mark Seemann has raised a slew of comments.
One of them is a note from Colin Jack about the annoyance of producing interface/implementation pairs all the time. My first response is that that sounds to me a bit like a problem with style. Maybe it's just wordplay, but usually I don't extract interfaces from classes, I implement interfaces that I've already discovered in some previous test.
My second response is to wonder how much this is a tools issue. I don't believe there's anything in the .Net world that yet matches the responsiveness of the usual Java IDE's. It makes a difference as to what's plausible. I remember the huge shift in perception when first VisualAge for Java and then JetBrains' Idea came out. In retrospect, I always spent more on time on rework than many people I worked with 1 but it sure took a lot more time in emacs (and I was pretty good at it), even if I was working in a better language.
1) which is not to say who was right, I'm just wired that way...
Labels: design, ide, testability
12 February 2008
New Tutorial at QCon
Labels: news
20 January 2008
Just when you thought it was safe to go back in the water...
Rising to the bait again (to keep the fishy metaphor going), there's yet another discussion of how the ability to hack the runtime changes the world. At one level that's true, but not in this case.
Dependency Injection is not a recent invention that a cabal of TDDers forced on the world, and it's absolutely not something that we came up with just so we could crowbar in our Mock Objects.
The relevant design guideline, named the Law of Demeter ("Strong Suggestion" isn't very catchy) was first described by Karl Lieberherr in 1987! The Mock Object pattern came from people applying their substantial O-O experience to TDD in Java, trying to figure out how best to avoid exposing implementation details in unit tests. One of the critical lessons from Demeter is that objects should have explicit dependencies. It helps to keep them focussed on their responsibilities and, as a result, easier to maintain—and a good way to make dependencies explicit is to pass them in.
Unfortunately, since then the world seems to have filled up with DI frameworks which cloud what should be a coding style discussion. This is not about having to configure every last corner of your application in XML, this is about how objects, or small clusters of them, get to talk to each other.
Roy asks, "[...] do you need DI all over the place, or just in specific places where you know dependencies could be a problem?" Well no—if you have enough foresight to know where those places are. I'm struggling at the moment to test against a framework where everything is helpfully packaged up nice and tight so I can't create an instance of one of its core objects. It's actually well written, but the authors just weren't good enough at prophecy to figure out my particular need. That's why I don't rely just on my intuition, I use the needs of my unit tests to help me figure out where the seams should be. To counter the FUD argument, I have absolutely no problem with saying that I don't want tools that do magic because I need guidance with my code.
As Roy (very politely) concludes, there isn't a high enough proportion of really good code in the world (some of it mine) that we should be in hurry to cut back on techniques such as DI. Just because something has been around for a while doesn't mean it's been superseded, especially in as conventional an environment as .Net.
Labels: design, testability
13 January 2008
Avoid mega unit tests
We've just had a posting to the jMock user list that included the following:
I was involved in a project recently where JMock was used quite heavily. Looking back, here's what I found:
- The unit tests where at times unreadable (no idea what they were doing).
- Some tests classes would reach 500 lines in addition to inheriting an abstract class which also would have up to 500 lines.
- Refactoring would lead to massive changes in test code.
I've seen this failure mode on another project I've been helping with, so I think there might be a common pattern.
I don't think any unit test code should get that large, except for unusual circumstances. Unit tests are supposed to focus on at most a few classes and shouldn't need a large amount of set up. What I saw on the other project was enormous amounts of preparation and positioning to get the objects into a state where the expected feature could be exercised. Of course it's hard to understand the point of a test when there's just so much code. If you see this pattern, then I'd suggest that the code (or at least the test code) needs breaking up a bit. On the other hand, integration and acceptance tests that happen to be written using a unit test framework might well be larger.
One thing I need to explore is whether the naive use of interaction-testing is particularly susceptible to this failing, or whether it happens all the time and we're the only ones who get complained to. I am, however, convinced that the emphasis on mainly using Mocks to substitute external systems (some of which I perpetrated myself in the early days) is a deeply bad idea which pushes teams towards the sort of problem described here.
Labels: design, listening to the tests, testability
22 December 2007
Mock objects for Java ME
Yesterday I uploaded a personal project, Hammock, to sourceforge (http://sourceforge.net/projects/hammockmocks). This a mock object framework for Java ME. Java ME, unfortunately, doesn't support reflection making jMock, EasyMock, etc. unsuitable for use on a mobile device or emulator. Obviously this makes creating mocks more difficult than with the Java SE mock object frameworks but the distribution includes a utility, HammockMaker, for creating the source for a mock object given a class file (of a non-final class or of an interface). The framework includes mocks of many of the standard Java ME classes for communications such as HTTP, SMS, OBEX, etc so that one can test without actually hitting a network. I've used it on three projects at my current employer.
Labels: news projects
04 December 2007
Don't tie down your code, use interfaces
Recently, I was responding to a question on the TDD list and found myself writing this:
Although there's only one use of the interface, you could think of it as a way of keeping a clean distinction between the naming of different domains (user and persistence). It makes managing package dependencies easier too.
which is what I've been meaning to say for ages. The reason we're keen on discovering interfaces in our code is that we think they're a good way of expressing concepts in a domain.
If I'm writing a ring tones store (the video store folded last year) and I need to look up, say, the customers that were active last month, I'll ask a SalesLedger object. So, what type is SalesLedger? If it's a class then my marketing module will have a dependency on whatever persistence framework I use. If my SalesLedger class is clean and delegates to some further persistence type then I've only deferred the problem. I'm importing, directly or indirectly, the persistence packages into my domain code. If I do the same with a few other frameworks (printing, display, network, etc.) then I'll have tied down my domain module with third party dependencies. The module dependency chain looks like this:
There are two problems with this, one obvious and one subtle. The obvious point is that I've raised the risk of inflexible code. One day I'll have to rip out a messy chain of dependencies to get to some code I want to change. Now, because I'm another World's Greatest Software Developer, I'll be careful not to embed the dependency too deeply and keep my packages clean and layered, but that's intellectual overhead I could do without. When SalesLedger is an interface, I have a clear distinctions between the domains of marketing and persistence, and I cannot let the abstractions leak through. If I do this for all my external dependencies, I can package up my domain code and use it in all sorts of ways I haven't thought of yet. The top-level Application then becomes a matchmaker, introducing all the modules to each other and helping them get started.
The more subtle issue is that SalesLedger, the interface I use in the body of my code, is in a different domain from the HibernateLedger, a class that implements it (and maybe some other interfaces). The code that uses a SalesLedger is concerned with marketing and payments, the code that uses a HibernateLedger is concerned with setting up connections. There may only be one implementation of an interface, but the two types are addressing different things.
Is this breaking YAGNI? I don't think so (although there are others that do). I'm not adding features I haven't identified a need for yet. Part of the deal with YAGNI is that I make the code as expressive of my current intentions as I can, and I think I'm expressing the needs of the domain code needs more clearly by limiting its dependency to an interface that defines just the services it needs from other parts of the system.
n.b. Gulliver image taken from news.bbc.co.uk
Labels: design
21 November 2007
MockObjects in Bangalore
I'll be presenting a tutorial/workshop/Dojo on TDD with Mock Objects at Agile India in Bangalore this Sunday. I hope the locals will find the material interesting (and that they give me an easy ride while I struggle with the jet lag :).
Labels: news
© The authors
