Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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?

share|improve this question
5  
Read :"Working effectively with Legacy Code." By Michael feathers. It's a cornucopia of information on this topic. –  George Stocker Dec 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ök Dec 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ök Dec 16 '11 at 17:16
show 1 more comment

4 Answers

up vote 4 down vote accepted

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:

  1. 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.
  2. Write wrappers for static classes that implement interfaces and inject those into new clients ala (1).
  3. Start gradually retrofitting your injected interfaces on former clients of static state/singletons and writing tests for them.
  4. Starting pulling functionality out of behemoth classes and testing the extracted, single-responsibility classes.
  5. Touched on earlier, but inject your dependencies!
  6. Write or factor to small methods and classes.
  7. Keep up the refactoring until you can finally kill the singletons/statics.

These are ways to make your code more testable (and, I would argue, better in general), but selling the process to others is a different matter. I would offer the following advice for that:

  1. Write tests and show people how to do it (the biggest barrier being a literal lack of knowledge of how to write tests).
  2. If you review people's code, write a unit test to expose a problem instead of telling them about it. Show them the failing test and task them with making it pass.
  3. Make sure your test runs execute quickly - long running test suites will rot and annoy people.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
share|improve this answer
add comment

Try asking Michael Feathers article Testable Java or Testable C# or C# TUFs and TUCs

The gist is ti avoid putting test unfriendly feature inside a test unfriendly construct.

share|improve this answer
    
@huMpty duMptythanks for fixing up the links –  Christian Horsdal Dec 16 '11 at 21:06
add comment

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.

share|improve this answer
    
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!) –  Murph Dec 16 '11 at 21:06
add comment

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:

  1. I start developing my classes
  2. 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).

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.