Alexander Beletsky's development blog

My profession is engineering

Approval Tests: Locking Down Legacy Code

Suppose, you working on project with a lot of legacy code inside. I know it makes you sick, but as brave developer you want to improve things. You met that ugliest method in your life and only one thing you want to do - refactor it. But refactoring is dangerous procedure. For safe refactoring you need to have good test coverage. But wait, it is legacy code. You simply have no tests. What to do? Approvals have answer.

Legacy code is the code that…

Works! Right, it is ugly, un-supportable, nothing you can easy change there. But the most wonderful feature of that code - it works for years. And first thing is to get advantage of that fact!

Here is my “just for example” legacy method.

namespace Playground.Legacy
{
    public class HugeAndScarryLegacyCode
    {
        public static string TheUgliesMethodYouMightEverSeen(string s, int i, char c)
        {
            if (s.Length > 5)
            {
                s += "_some_suffix";
            }

            var r = new StringBuilder();
            foreach (var k in s)
            {
                if ((int)k % i == 0)
                {
                    r.Append(c);
                }
                else
                {
                    if (k == c)
                    {
                        if (r.Length <= 2)
                        {
                            r.Append('a');
                        }
                        else
                        {
                            r.Append('b');
                        }
                    }
                    if (k == '^')
                    {
                        r.Append('c');
                    }
                    else
                    {
                        r.Append(k);
                    }
                }
            }

            return r.ToString();
        }
    }
}

(it’s it ugly enough?)

It has a cycles, nested if-else case and all nice features of legacy code. We need to change it, but in the same time guarantee it would not be broken.

Trying first simple test

Supposing also, I’m not much in details of how exactly this function works.. So, I’m creating the test like:

[Test]
public void shoudl_work_some_how()
{
    Approvals.Approve(HugeAndScarryLegacyCode.TheUgliesMethodYouMightEverSeen("someinput", 10, 'c'));
}

I run it and got some result to approve:

approvals

I approve that, cause I know that function works. But something inside tells you - that is not enough. Try to run in under the coverage:

approvals

That does not make me real confident with tests only one hit and coverage 76%. We have to create better tests cases.

Use combinations of arguments

Approvals include some tools to deal this case. Let’s change out test and write something like,

[Test]
public void should_try_to_cover_it()
{
    var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
    var strings = new[] { "", "approvals", "xpdays", "^stangeword^" };

    ApprovalTests.Combinations.Approvals.ApproveAllCombinations(
        (s, i, c) => HugeAndScarryLegacyCode.TheUgliesMethodYouMightEverSeen(s, i, c),
        strings,
        numbers,
        chars);
}

With only few lines of code, I’ve got 1560 test cases and all of them are correct!

approvals

Besides, I got pretty good coverage. Ideal, I would say. Now, if even one small change would happen, some of 1560 tests will notice that.

approvals

Locking down

The process of controlling the legacy code in that way is called “Locking down”. After the code is locked down, you have high confidence (read low risk) of breaking changes you introduce. Please note how low effort it was to create all that 1560 tests and how much value gained in that.

Notice, that test like should_try_to_cover_it is not supposed to “live forever”. You probably even don’t need to check it in to source control. You just do your job, either refactoring or changing that functionality and use Approvals to notify you as fast as possible of something goes wrong.