I have ran into this a few times where you get put on a project and you want to do unit testing of some type, but the project isn't setup right to allow for this and you might not have buy-in from everyone. The main issues tend to be code smells like issues with dependencies (especially the over use of statics) and seperation of concerns. New code that you write can be setup easily, but anything that operates with other legacy code or even those not drinking the cool aide can become an issue.

Some things that I have found are that isolation frameworks like TypeMock can make this easier. What are some other tools/tricks/best practices that you have found that ease this sort of pain?

Read :"Working effectively with Legacy Code." By Michael feathers. It's a cornucopia of information on this topic.
–
George StockerDec 16 '11 at 15:24

1

@GeorgeStocker If I had a nickel everytime somebody referred this book on this site I could retire!
–
maple_shaft♦Dec 16 '11 at 15:29

8

@maple_shaft, if I had a nickel everytime somebody wrote untestable code, I could resolve the current global financial crisis...
–
Péter TörökDec 16 '11 at 16:27

1

@PéterTörök Yes but most of us would be out of work though... :)
–
maple_shaft♦Dec 16 '11 at 16:38

@maple_shaft, why, I didn't say all that untestable code would disappear or anything... my feeling is that making such a colossal amount of information entropy disappear would have some nasty side effects on the balance of the universe anyway :-)
–
Péter TörökDec 16 '11 at 17:16

4 Answers
4

Wow, this describes a recent situation of mine exactly on a large, existing project. No unit tests at all, and reluctance toward/skepticism of the idea. At the time of writing, I've been able to work with another person who favored it to get it more accepted (though he and I are largely the only ones that generally write tests). The code base was (still is) heavily laden with procedural static code, but it's getting better. Here are some of the things that I did to improve the situation:

Immediately interface all singletons, especially ones in heavy use. Then, inject the interface into the new client classes you're writing to make them testable.

Write wrappers for static classes that implement interfaces and inject those into new clients ala (1).

If the application is large, show people that the feedback loop is much faster for modify-test-refactor than it is for modify-build-run-maniulate-gui-observe-refactor.

Unit test a feature that you write and keep objective statistics (defects reported per LOC) to compare to non-tested code. This is a win with management.

Along those same lines, run static analysis/metrics on the unit tested code which, in my experience, tends to score better than non-tested code. Objective metrics are hard to argue with.

Point out to people that guarding their code with unit tests makes it harder for someone to come along and mess with their code in defiance of their original intentions. Sounds petty, but this is often a selling point for people when they realize it.

Product implementation documents or writeups for consumption by stakeholders that emphasize code coverage or number of tests. This creates the (accurate) impression that your code is demonstrably reliable and managers/analysts/PM types might start asking others where their coverage/test metrics are.

Finally, and this is the one that I've found to be most eye opening or other developers, demonstrate the power of unit testing (specifically TDD for this one). Pull off a coup like a major refactoring of your work in less than a day or having your code execute perfectly the first time you put it all together and run the application. Developers who code it up and then spend a few days in the debugger programming by coincidence will be sobered by this in a way that they won't be all the unit test proselytizing in the world.

Do not be hung up on the form your unit tests take, because there are many non-standard ways of doing this. Heck, you can write a stand-alone app which massages the database and looks for inconsistencies (in case some key constraints are too slow). You can test things from within a main method without formal unit test libraries. You can put together a quick Python script.

What you want to do is look for low-hanging fruits - e.g. things that are easy to test, likely to contain bugs, and are important.

When you succeed, advertise the success. Once you can demonstrate that your scripts and stuff can catch many bad bugs quickly, then take it to the next level.

Ask for permission to re-factor some code. Remember, critics will still be skeptical and will want to see tangible results, so you need to keep finding bugs with it. If you have to, make it find bugs that you actually discovered manually. Do not over-engineer stuff just because you like cleaner code. Multiply your hourly salary by the number of hours you spent on this - now you need to justify this investment. That is how many others see it.

Also, try to generate a lot of buzz slowly, so that people will start thinking that these ideas are actually theirs and not yours.

Solid advice - fundamentally if it ain't broke... so try to take the opportunities when they arise as part of your normal work. Obviously you can unit test new stuff, for existing code work the tests in when you need to fix things or add to them. Refactoring when adding new features tends to be a particularly good opportunity (providing you don't get carried away!)
–
MurphDec 16 '11 at 21:06

I don't want to sound too pessimistic here, but if on the same project you're the only one of quite a few who is unit testing, you might as well not do it at all. Compromise on integration tests, even if they're less accurate and slower.

The reason is that you're going to find yourself with a code base that's only partially-tested, inconsistent and hard to follow. And that's much worse in my book than a project without unit tests. Again, remember that integration tests are still possible.

The better alternative would be to persuade them that unit-testing will make their life easier and help them create a better product.

If you still choose (for some odd reason) to unit-test, what I do with legacy code goes pretty much like this:

I start developing my classes

When I need a legacy service, I create an interface for it, and then create an adapter that implements that interface and delegates all work to the actual legacy service.

That way I can work with legacy services and mock them in my classes' tests, without changing any legacy code.

And if you need to fix stuff in legacy code, slowly turn it to testable code - starting from integration tests and slowly and carefully turning them into unit-tests (only of the part you're fixing). Do that and you'll slowly have more and more parts of the code base that are testable (and tested).