7

They want us to develop new features of a product with TDD. I know that they don't usually write tests for legacy code of already developed modules.

But yet, what if new features have to be added to the legacy modules and we still want to stick with TDD?

Vladimir Stazhilov
  • 457
  • 1
  • 3
  • 7
  • 4
    There is a book that helps: Working Effectively with Legacy Code, by Michael Feathers – Marcelo Glasberg Feb 07 '15 at 19:50
  • possible duplicate of [What is the most effective way to add functionality to unfamiliar, structurally unsound code?](http://programmers.stackexchange.com/questions/135311/what-is-the-most-effective-way-to-add-functionality-to-unfamiliar-structurally) – gnat Feb 07 '15 at 20:13
  • 1
    see also: [How can I update a large legacy codebase to meet specific quality standards?](http://programmers.stackexchange.com/questions/185130/how-can-i-update-a-large-legacy-codebase-to-meet-specific-quality-standards) – gnat Feb 07 '15 at 20:13

2 Answers2

10

The problem you may quickly hit is that in order to test a particular piece of code, you should make testable the code surrounding it. Changing the surrounding code requires testing as well, which, in turn, requires even more changes.

Practical example. A year ago, I had to work on a... let's call it legacy system. Millions of lines of C# code. Maintainability index (MI) reaching sometimes zero (hint for those who never used MI in Visual Studio: going below the MI of 50 is quite difficult: you really have to start writing spaghetti code on purpose). Methods containing up to 4 000 LOC of procedural code. And obviously zero tests, because, frankly, everybody knows writing and maintaining tests costs money.

Since adding the most basic feature was causing hours or days of wasted time (plus time wasted resolving bugs discovered later in production, then days fixing the bugs introduced when resolving the previous bugs), I was thinking about adding at least a bit of testing.

When I left a project, there were indeed a few hundred tests. Those tests were covering the parts of the code which I put in separate classes and which were independent enough from the remaining code base. This is easy enough to do, but also not very useful. Yes, those tests covered some business rules, but their scope was very limited.

I was also thinking about adding more substantial testing, but unsuccessfully.

  • The major problem was that nearly every piece of code was relying on the database. I tried, on paper, to abstract the database in order to be able to replace parts of it by mocks and stubs. On paper, it worked, but it would require a tremendous amount of time to actually implement.

  • Another problem, obviously, was the spaghetti aspect of the code. How would you unit test a 4 000 LOC method which needs 16 parameters to be passed to, given that those parameters are not documented (and nobody really knows how some of them should they be used), and trying to replace them by stubs causes weird bugs in weird locations?

So yes, you can introduce tests, including test driven development, on the parts which are being changed, but if the code base is a mess, the scope of those tests will be too small for them to be particularly useful.

What I can personally suggest is to start refactoring modules one by one (hopefully they are interacting through interfaces and are independent enough from each other otherwise), and ensuring those modules have tests. Once you have sufficient code coverage, you'll notice that testing new features is much easier.

Arseni Mourzenko
  • 134,780
  • 31
  • 343
  • 513
2

I've had a similar experience like MainMa described in his answer.

I've started working in the middle of a project that had a spaghetti code, a few unit tests but with some broken or commented, many god classes and a bad architecture design that required the developer to change 10 or 12 files just to add a DropDownList that retrieved data from the database.

When you try to use TDD in a project like that, you'll have a hard time to remove tightly coupled code, mock things and the scope of your tests may end up very limited. Refactoring is the best solution, but we know that if the technical debt is really high, no one is willing to pay for it.

However, even in maintenance projects, some features will be large enough to require a new Service or a new Web Page. When the time comes, take the opportunity to build it using TDD, good design patterns and to use a new architecture, if necessary. The objective is to break the loop of bad code and stop copying-paste messy choices.

With this approach, you will not pay the technical debt, but you can reduce it in absolute terms and, more important, overwhelm it in relative terms.

In a few months we had two distinct sides inside the same solution: one healthy that served as an example for junior devs to continue the work and another one that we called the "sick" part.

TL;DR

Refactoring old code to allow TDD is the best solution, but your manager may not authorize you to spend your time with this. At least, try to build new features/modules with good practices instead of copying-paste ugly code.

Zanon
  • 329
  • 2
  • 3
  • 16