I'm not going to write down the whole development story -- it would take months, and I'll never finish the project. So far, we have seen several key principles of test-driven development, as well as some cool design ideas. Let's review them:
1. Test-first: write a test, watch it fail, write the simplest (production) code to make the test pass (you can even hardcore some constants in your code), then refactor in smallest possible steps while keeping the test (and the other tests) pass. This is referred to as Red-Green-Refactor cycle.
2. Test-driven: when your testing is too complex, don't just sigh and accept it. This is a sign that your design can be improved. Let the testing drive your code.
3. YAGNI -- you ain't gonna need it. Instead of implementing a Good Thing just because it is one of the GoF patterns or you've just read it in somebody's blog, whatever makes your tests pass is OK. You see that this method you just wrote is ugly, but wait until the next test drives you in the right direction for refactoring. There's another similar principle, KISS (Keep It Simple, Stupid).
4. Separation of Concerns (SoC) principle -- each object has to have a distinct responsibility. Keep in mind, though, that this is just a clue: whenever you have problems with testability and you don't know what to do, try this or some other design idea (see Dependency Injection below) and see if it simplifies things. The general idea is that you don't apply these patterns or principles blindly, but let your tests decide what to apply.
5. Dependency Injection. We have seen it when we were getting rid of the Graphics dependency. The main idea is that it is very hard to test your class when there's an external dependency involved, such as an SMTP server, local file system, or some unmanaged stuff. So, don't hardcore it into your component, have it "injected" somehow in your production code, and mock it in your test code to make sure that your code interacts correctly with it. Again, the "weakness" of our mock framework dictated this. But this is not just designing for testability -- turns out that it opens a possibility for other reporting options -- exporting to HTML, Word, or maybe projecting onto a Star Wars style transparent screen.
This is no accident that Rhino Mocks forced us for a better design. While TypeMock does its job by applying some sort of black magic, Rhino plays by the .Net rules: overrides virtual methods, extends classes, implements interfaces -- does what you or users of your component would do to extend it.
So, while I'm not going to publish all the details, I'm going to share all my big moves and discoveries, especially TDD-related. The latest source of FreshReports can be found in the SVN repository at SourceForge at https://freshreports.svn.sourceforge.net/svnroot/freshreports/trunk.
TDD Design Starter Kit by Jeremy D. Miller
Achieving And Recognizing Testable Software Designs by Roy Osherove
testdrivendevelopment group on Yahoo