back

by jader201·11y ago·view on hn ↗
Nice primer, but it omits a crucial part of "taming a testless code beast": Making it testable.

Before you even get to step 1, chances are that if you already have a "testless beast", it's not testable. That is, the right abstractions aren't in place to be able to properly mock or fake your dependencies.

To me, this is one of -- if not the -- hardest part(s) of adding tests to an already existing codebase. This is particularly true when dealing with a statically-typed language.

This is why you'll often read how important it is to start an application with testing in mind: as you write your tests, you'll be forced to make your app testable. Otherwise, it's extremely easy to forego some of the necessary abstractions needed for testing -- and conversely, extremely hard to make sure you have all the necessary abstrations in place if you don't include testing from the beginning.

So this article shouldn't be read thinking that it's "that simple" -- the hardest part isn't even mentioned here.

4 comments
In the past two days I've dealt with just such a beast, and the answer is simple: start with just one, whole system test. If you've got an output file which is "known good", it can be as simple as running the program and doing a diff between the "known good" and the output, then start making changes. Automate this test, then start refactoring mercilessly, adding tests as you go.
It is really a chicken and egg problem. On the one hand, it would be good to refactor the code to help write some tests. On the other hand, you really shouldn't refactor the code with tests to prove that you haven't broke anything. In either case, you are correct that it can be extremely difficult.

While there is no easy answer, I've found that in these situations, I still try to write a test first. The difference being that I try to write a test that exercises the code at some API level that I don't want to break. These test start off really ugly and require a ton of work, but they ensure that refactoring the lower level code to make it more testable doesn't break things. Eventually, the tests and code at the higher level can be updated safely.

As far as tests are a descriptor of the business logic, another really difficult part that is also omitted from this article actually figuring out who the person in the organization that can authoritatively tell you what the business logic is can be really tricky. Sometimes that person no longer even exists. Simply writing tests that test that the existing code does what it does is not always productive.
I hope people don't expect it to be "that simple", there's at least one whole book on the subject ("working effectively with legacy code") and it though it stands on its own, it builds on predecessors (Refactoring)