23
  • I'm talking about unit tests in the TDD sense. (Not automated "integration", or what you like to call it tests.)
  • Legacy code as in: (C++) code without tests. (see: Michael Feathers' Working Effectively with Legacy Code)
  • But also legacy code as in: Code that our team has been working with for the last 10-5 years, so we very often have quite a good idea of where to put things to change something.
  • We do have unit tests in place (via Boost.Test) for some modules that came later or have been a "natural" fit for unit tests (common app specific containers, string-stuff, network helpers, etc.)
  • We do not yet have proper automated acceptance tests.

Now, recently I had the "pleasure" to implement 3 new user-facing features.

Each of those took me about 1-2 hours of getting up to speed with the code parts I needed to change, 1-2h hours to implement the (little) code I needed to change and another 1-2 hours to make sure the app ran correctly afterwards and did was it was supposed to do.

Now, I really added little code. (I think one method and a few call lines for each feature.)

Factoring out this code (via any of the methods suggested in WEwLC), so that a unit test would've make sense (and not been a complete tautology) would have easily taken another 2-4 hours, if not more. This would have added 50%-100% time to each feature, with no immediate benefit, as

  1. I did not need the unit test to understand anything about the code
  2. Manual testing is the same amount of work, as I still need to test if the code is correctly integrated into the rest of the app.

Granted, if, later on, "someone" came along and touched that code, he theoretically could have some benefit from that unit test. (Only theoretically, as that tested island of code would live in a ocean of untested code.)

So, "this time" I chose to not do the hard work of adding a unit test: The code changes to get that stuff under test would have been significantly more complex than the code changes to get the feature correctly (and cleanly) implemented.

Is this something typical for strongly coupled legacy code? Am I lazy / do we set the wrong priorities as a team? Or am I prudent, only testing stuff where the overhead isn't too high?

1
  • what if some other department will take over your "well known legacy code"? What the guys there will do with it? Commented Nov 23, 2013 at 12:18

7 Answers 7

20

You have to be pragmatic about these situations. Everything has to have a business value, but the business has to trust you to judge what the value of technical work is. Yes, there is always a benefit to having unit tests, but is that benefit great enough to justify the time spent?

I would argue always on new code but, on legacy code, you have to make a judgement call.

Are you in this area of code often? Then there's a case for continual improvement. Are you making a significant change? Then there's a case that it is already new code. But if you're making a one-line code in a complex area that will probably not be touched again for a year, of course the cost (not to mention risk) of reengineering is too great. Just slap your one line of code in there and go take a shower quick.

Rule of thumb: Always think to yourself, "Do I believe that the business benefits more from this technical work that I feel I should do than the job they asked for which is going to be delayed as a result?"

4
  • 1
    What is a good piece of code? One that is well designed, tested, documented, and future proof or one that you get paid for? As always: you can have it good; you can have it cheap; you can have it fast. Pick any two, the third one is your cost. Commented Oct 24, 2011 at 10:19
  • 2
    "I would argue always on new code" ... "new code" in a legacy codebase or "new new code"? :-)
    – Martin Ba
    Commented Oct 24, 2011 at 10:25
  • pdr has the right idea. It's an engineering decision (one with tradeoffs). Generally if you have a big piece of vetted code it is a good idea to leave it alone. I've seen a TDD effort on legacy code (only a year, but that's enough) end up costing months and only ended up spending money and breaking a legacy app for months until the TDD refactor-introduced bugs were fixed. If the code/feature is done and tested, don't go breaking it to add instrumentation (TDD) to tell you something you already knew anyway (that it works).
    – anon
    Commented Oct 24, 2011 at 16:31
  • @Sardathrion-againstSEabuse: fourth aspect is scope. Ideally, you can limit the scope to max the other three.
    – serv-inc
    Commented Sep 7, 2022 at 7:24
10

The gain of unit tests in TDD sense is to get something that stands by itself, without needing oral tradition built over years by a stable team.

It is a big luck that the same team has been on the same project for so many time. But this will eventually change. People get sick, bored or promoted. They move, retire or die. New ones come with new ideas and the urge to refactor everything the way they learned at school. Companies get sold and bought. Management policies change.

If you want that your project survive, you have to prepare it for changes.

0
4

Writing unit tests is future proofing your code. If the code base does not have tests, then you should add new tests for all new features because it makes that bit of the code just that little more robust.

I find that adding tests, even in legacy code, is beneficial in the medium to long run. If your company does not care about the medium to long term, I would look for another company. In additions, I do not think that it takes longer to test manually than it take to write a set unit test. If it does, then you need to practice code kata focused on writing tests.

6
  • 1
    But, but, but! :-) That little should translates to a 100% increase in time needed to implement a new feature. It does not decrease the time needed to implement subsequent features. It may decrease time needed when changing stuff.
    – Martin Ba
    Commented Oct 24, 2011 at 9:25
  • 3
    You are making sure that changes in that area of the code do not introduce new bugs, that new (less experienced users) can get a clear understanding of the code, and that side effects affecting that area are cough at testing time instead of at release time. Testing help verify that code does what it should do not make it easier to add features later. I find that adding tests, even in legacy code, is beneficial in the medium to long run. If your company does not care about the medium to long term, I would look for another company. Commented Oct 24, 2011 at 9:32
  • 1
    @Martin because clearly what you do is code what you think the feature should be, and then ship it. No testing is done at all at any stage... no, wait. As developers, we all test our code (at least by hand) before saying it's done. Replacing "by hand" with "by writing an automated test" is not a 100% increase in time. Frequently, I find it's about the same time taken.
    – Kaz Dragon
    Commented Oct 24, 2011 at 10:14
  • 1
    @Kaz - "Replacing "by hand" with "by writing an automated test" is not a 100% increase in time. Frequently, I find it's about the same time taken." - YMMV, but for my codebase, this is clearly wrong (as laid out in the question).
    – Martin Ba
    Commented Oct 24, 2011 at 10:23
  • 3
    @Kaz: What I :-) meant was: A unit test tests my code in isolation. If I do not write a unit test I don't test the code in isolation, but only if the code is called correctly and produces the desired result in the app. This two points (called-correctly and app-does-what-it-should) I have to test anyway (manually) and are not covered by a unit test. And the unit test wouldn't cover these two points but would "just" test my function in isolation. (And this unit-test-testing would be additional and wouldn't be offset by anything as far as I can see.)
    – Martin Ba
    Commented Oct 25, 2011 at 10:23
2

Certainly best practices dictate you should unit test any code you change. Real world coding however involves a lot of compromises, time and materials are finite.

What you need to do is evaluate the cost in real terms of fixing bugs and making modifications without unit tests. You then can asses against the investment in creating those tests. Unit tests greatly reduce the cost of future work but come at a price now.

Bottom line is if your system is rarely changed then it may not be worth writing unit tests but if it is subject to regular changes then it will be worth it.

1

All of these little rational decisions to not create tests are building up a crippling technical debt that will come back to haunt you when somebody leaves & a new hire has to come in & get up to speed.

Yes, it might have cost another 2-3 hours to write the unit tests but if the code gets sent back from testing you'll have to manually test everything again, and document your testing, again - you do that don't you? Otherwise how does anyone know what you tested and what values you used?

Unit tests document the expected behaviour of the code the test data used to check the functionality and gives new coders reasonable confidence that they haven't broken something when making changes.

It costs a couple of hours today more than manual testing but then you are testing every time you do any change saving hours over the life of the code.

However, if you know that the code will be retired in a couple of years all I've said changes because you won't finish putting tests in the code & it'll never pay back.

3
  • "and document your testing, again - you do that don't you?" - {putting cynical hat on:} Of course not. We are not in developer fairy land. Stuff gets implemented and tested and shipped. Later on it gets broken and fixed and tested and shipped.
    – Martin Ba
    Commented Oct 24, 2011 at 10:17
  • I would hope that everything gets manually tested regardless of the number of unit tests. Automated acceptance tests are another matter entirely. But then acceptance tests are not made more difficult by the codebase being legacy, so are out of scope here.
    – pdr
    Commented Oct 24, 2011 at 10:19
  • 2
    mcottle - You are missing a vital point. The unit testing will not decrease the time taken to do "manual functionality testing" of the finished feature. I still have to click-through all the dialogs involved with a prudent subset of possible inputs to make sure the actual finished app as built does what it's supposed to do. (I grant that theoretically unit testing may help to limit this manual test cycle to only one loop if the unit tests make sure that I won't find any obvious problem only during manual testing.)
    – Martin Ba
    Commented Oct 24, 2011 at 10:22
0

The value of unit tests does not only lie in testing a new feature when that is developed. The benefit of unit tests is gained mostly in the future.

Yes, you may have to spend what seems like an inordinate amount of time to make your legacy code testable.

But if you don't, you will, or certainly should, spend many, many, many more hours testing the feature. Not just on its initial development, but with every new version of your software. Without unit tests and other automated testing, you do not have any other option but to spend many hours of manual testing to ensure that your feature was not inadvertently broken, even when that feature itself was not changed.

2
  • 1
    "Without unit tests you do not have any other means to ensure that your feature was not inadvertently broken, even when that feature itself was not changed." -- Unit tests won't help. To make really sure, I'd have to have automated integration / acceptance tests.
    – Martin Ba
    Commented Oct 24, 2011 at 10:33
  • Yes, unit tests are only the first step. Edited answer to clarify Commented Oct 24, 2011 at 10:49
0

Each of those took me about 1-2 hours of getting up to speed with the code parts I needed to change, 1-2h hours to implement the (little) code I needed to change and another 1-2 hours to make sure the app ran correctly afterwards and did what it was supposed to do.

So you tested that the changed code did what you expected it to do.

Did you also check for:

  • All the things that make it fail, or
  • Things that you might not have expected it to do?

Tests do not prove the correctness of your code today. You do that by writing the code to do what it needs to do and that's why it feels like "wasted" effort to do the job "twice"; once to write the code and again to write the tests.

Tests can prove the continued correctness of your code over time. You (or somebody else) will make changes to the code over the months and years that follow, that's just the Nature of software, and your tests will ensure that you (or they) don't break anything that you didn't intend to.

Manual testing is the same amount of work, as I still need to test if the code is correctly integrated into the rest of the app.

Once the tests are written - and tested, obviously :-) - then the amount of "work" they take reduces to practically Zero. You just run the tests. Any amount of manual testing will almost certainly be more than this.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.