Working on the new code for handling optional struct members in XML-RPC.NET Ive been following a strict unit testing approach of coding tests then implementing the code to pass them. This process highlighted the inadequacies of my existing unit tests. I realized that the tests should not be just concerned with the external interface of a component but should also be much finer grained, for example testing functionality implemented in private methods.
The problem with this is how do you give the test harness, NUnit in my case, access to the private methods. Im currently using #ifdef DEBUG to add public visibility to the required methods in a debug build. This is not ideal because there may be significant differences between the release and debug builds. When I get the time Ill add another build which only affects method visibility and so minimizes any differences. But for now Im concentrating on adding tests retrospectively to any areas which are affected by the optional struct member changes.
In the day job weve been discussing testing recently and Ive noticed that unit testing is mostly seen as something which is performed after the code is written. No wonder the unit testing actually performed is often very inadequate. Once youve written the code, writing and running unit tests are likely to be seen as unnecessary burdens, particularly when there are pressures to announce you are code complete and ready for integration testing with other components.
On the other hand, if implementation is driven by tests you get an immediate benefit from the small incremental effort of implementing them, each piece of code being checked as you implement it and the tests as whole providing continuous regression testing. This also removes the need for the separate unit testing phase at the end of implementation which is all to easily skipped.
The main problem with the test-driven approach is that it is often difficult to write tests where external dependencies are involved. I think the key here is to treat testing as a major driving force at the design stage, always choosing the design approach which allows individual pieces of functionality to be tested as they are implemented, using stubs to emulate external dependencies. Testing should not be a separate section tagged on to the end of the design document, almost as an afterthought, but a concern running through the whole document.
None of this is new but it can be difficult to go from vague approval of the concept of test-driven development to actually doing it. Im just beginning to work through some of the issues in something which actually can make a big difference to the way developers work.