Wednesday, December 9, 2009

Some test-driven-development notes

A couple of random points that might be of interest:

Code coverage tools

  if ( rare-condition ) {
      -is this code tested?-
  }
If you actually followed test-first, then the code in the rare if is definitely tested, because if there isn't failing test case for the rare condition, then there is no reason for the code or the test to exist.

Another objection could be that people won't follow the techniques. I haven't found this to be a big or recurring practical problem so far, and agile techniqes tend to be empirically driven. If you suspect that this is a problem you are seeing in your environment, running a code-coverage tool to put some data behind your suspicion may be a good idea.

Test before or test after?

Note that the solution to the code-coverage question above does not work if tests are written after the fact: in this case, the rare-case is likely not to be covered because it was written without being forced by a failing unit test.

Many if not most of the benefits of TDD are related to the way they shape the design of the code, all of these benefits obviously don't accrue if you've already designed or even written the code. In fact, if you ask the XP folks about it, they will tell you that TDD is not for ensuring quality, it is exclusively for helping with coding and design.

For example, figuring out how to test something will force you to come to a clarity about what the code is supposed to do that just writing the code usually does not.

Knowing that your tests cover your code (see above) allows you to do extremely radical refactorings at any point in the development process. The ability to refactor at any time in turn allows you to keep your initial designs simple without coding for anticipated changes. Not coding for anticipated changes that may not occur or may occur differently than you expect in turns allows you to move more quickly, which more than pays for the expense of the tests.

Furthermore, the tests force you to think how you can call the functionality you are about to implement, which means it shapes architecture towards simplicity, high cohesion and low-coupling.

Generating tests

Auto-generating tests for existing methods is a means of subverting the test-driven approach: there will be the appearance of testing, but with virtually none of the benefits. It is probably worse than not having tests, because in the latter case you at least know that you're not covered.

Is it a good way of starting with unit test coverage for legacy code? No. See the C2 wiki entry for a good explanation of how to approach this case. In short, start refactoring and adding unit tests when you actually need to touch the code, be it for new features or to fix defects that are scheduled to be fixed.

Labels:

Wednesday, December 24, 2008

More on one test class per class

I knew there was some other place this recommendation had recently popped up: "Emergent Design" by Scott Bain, via Ralph Johnson.

Labels:

Saturday, December 20, 2008

Unit test the class

Travis Griggs comes to the conclusion that unit test objects should map 1:1 to classes under test.

I agree.

In fact, I would go a bit further: tests should be an integral part of a class. While this helps avoid negative outcomes such as parallel class hierarchies or having code and tests diverge, it more importantly simplifies the test/code relationship and drives home the point that code is incomplete without its tests.

While I was working with JUnit on a reasonably large Java system, both finding a good place for a particular test and finding the tests for a specific class became quite burdensome after a while.

For this reason MPWTest simply asks classes to test themselves. Furthermore, only frameworks are tested, so the test tool simply loads each framework to test, enumerates the classes within that particular framework and then runs the tests it finds. TestCases and TestSuites are implicitly created from this structure, removing most of the administrative burdens of unit testing, and also any explicit dependence of the tests on the testing framework.

Having no dependencies on the testing framework makes it easier to ship tests in production code without having to also ship the testing framework. While this may sound odd at first, it avoids potential issues with code compiled for testing being different than code destined to be shipped, and further reinforces the idea that tests are an integral part of each class, rather than an optional add-on.

Labels: ,