Applying Working Effectively With Legacy Code

Having recently read “Working Effectively With Legacy Code” (WEWLC) my work provided me the oppertunity to put it into practice. I had been handed a small program to maintain : it has a few issues that needed fixing. The product is written in VB.NET, uses a mixture of module level functions and classes to acheive it’s aims. There is no clear design or documentation and it relies upon COM calls to an external API of a product that I do not have a licence for (and it would be expensive to obtain). It had no unit test coverage. By all definitions this is Legacy code.

To be fair the application did have a set of wrapper functions that were used to access the API.
The first stage was to put these methods into a class and use “Lean On The Compiler” to find where to create the class. Having done so I was able to add some methods to the class so that it provided a better abtraction from the COM API (the same three COM calls were being made in sequence across the App). Once I had these I was able to reduce a fair amount of duplication.

Having extracted a class that access the COM API (which I could not yet unit test) I was able to perform “Extract Interface”. Given that COM API’s are extensively interfaces they are very easy to mock (except that the interfaces are execisive wide). By mocking one of the COM interfaces I was able to re-implement the extracted interface. This finally allowed me to get some of the application under NUnit.

Eventually the bug turned out to be in the data access code – there was a remove method with an optional second parameter. The second parameter controls whether to remove the item from the underlying database.

By using the techniques in WEWLC I was able to get an unwieldy application at least partially under unit test. However I was not quite able to meet the stringent requirements of unit tests taking less than 0.01 seconds. In addition the policy of not touching the database in the unit test meant that I had not been able to find the given bug directly. I had managed to prove that the method that called it was otherwise working.

I can see WEWLC becoming a classic along the lines of Refactoring.
It provides the answers to the questions that would prevent most people from attempting to get their code under a test harness and thereby allows TDD to be used on non-greenfield projects.

Unit testing stored procedures

Here is a xUnit suite for stored procedures in SQL Server.

Here is the version for Oracle

This is a slight pain having two suites for the various databases.

I am not sure what the author of “Working Effectively WIth Legacy Code” would make of these.
His definition of a unit test involves not touching a database – which would make this difficult.

Real World Unit Tests

Theoretically all of your code should be covered by unit tests (although it is common to ignore simple properties). For help with this see NCover hopefully this will soon get a VS.NET addin.

If you are really lucky you are working on a greenfield project that has clear static requirements, a simple yet detailed design document, a full set of acceptance tests and plenty of time to write the unit tests. However in the real world you don’t have any of these.

Typically you will have an existing code base, some of which is covered by unit tests and the others not.

You need to prioritise where to write the unit tests.

Carefully constructed tests can excercise a large part of the system in a few tests.
For example if you use a Facade to import data into the system (considered an atomic step, but actually exercising a lot of validation, parsing and persistance code) you can exercise the most frequently used 25% of the system in a handfull of tests.

If a test fails you can use other smaller more detailed tests to narrow down the error.
The only downside to this is that a single error in a fundamental system can cause hundreds of failures.

If one area of the system has failed frequently over a number of builds then it is an obvious target for some new unit tests.

If any area of the system includes scripting, dynamic sql  or stored procedures they need to be called at least once by the unit tests. This will excercise the database creation script during either the build process or by the Continous Integration server (You do create a new database as part of your build process don’t you?). Anything that is not checked by the compiler is ripe for typos to cause havoc. Stored procedures are great – when compiled they report certain errors (the sql does not make sense) however they can ignore the fact that tables or fields do not exist.

Tests need to be fragile with respect to the functionality tested, but be immune to minor changes in call symantics. This is where some simplifying Facades really help – they isolate the test code from change, encourage reuse, make the test code easier to read and can provide ideal debugging fodder (either after the test has been run or set breakpoints and run a single test).