简体   繁体   中英

Advice needed for refactoring legacy code

I'm working a legacy code base and I would to use TDD to add new features to the code that I'm currently changing.

Please note that the current code base does NOT have any UT'S.

I have a Calculator class with the following implementation:

public final class Calculator extends CalculatorBase {
    public Calculator(Document document) throws Exception {
        super(document);
    }

    public int Multiply(int source, int factor) {
        return source * factor;
    }
}

This class inherits from the following base class:

public class CalculatorBase {
    public CalculatorBase(Document document) throws Exception {
        throw new Exception("UNAVAILABLE IN UT CONTEXT.");
    }
}

NOTE: The constructor does actually a lot of things, which I would like not to do in UT's. For simplicity, I've made the constructor throw an exception.

Now I want to add an 'Add' function to the Calculator class. This function looks like:

public int Add(int left, int right) {
    return left + right;
}

A UT for this particular piece of code should be very straightforward.

@Test
@DisplayName("Ensure that adding numbers DOES work correctly.")
void addition() throws Exception {
    // ARRANGE.
    Calculator calculator = new Calculator(null);

    // ACT.
    int result = calculator.Add(1, 1);

    // ASSERT.
    Assertions.assertEquals(2, result);
}

Since the constructor of the CalculatorBase base does throw an exception this unit test will NEVER pass.

The tricky part to make this testable is that the CalculatorBase class is auto generated by a tool, thus the source code of that class cannot be modified.

What (baby) steps should I take to make ensure that the Add method on the Calculator class can be tested? The goal is to make the whole project testable, and even get rid of the auto generated stuff, but I would like to use TDD wherever possible in order to refactor the code gradually.

One could argue that I can make the Add method static since it doesn't use any dependencies of the Calculator class, but the code is just quickly added together. The Add function is, in the actual scenario, something else which does consume the state of the Calculator class.

You can:

  1. Create the new method as static
  2. Create a temporal alternative constructor commented as "Only for testing"
  3. Refactor the class to remove the dependency
  4. Suppress it with PowerMock

But the safest way to do it is to create an scaffold test. This build the class even if the constructor has dependencies. You may need to break the encapslation with setters just for testing. Once the class is really well tested you may refactor the class, add better tests and finally remove the dirty scaffold test. Depending on the complexity of the class this may be appropiate or an overkill.

You can create a new protected or package-private static method add with an extra Calculator parameter (at least for now, until the class can be easily instantiated), and create your test using Mockito :

class Calculator extends CalculatorBase {

    private final int limit = 100; // to show that we need an instance state in add method 

    ....

    public int add(int left, int right) {
        return add(this, left, right);
    }

    static int add (Calculator calculator, int left, int right) {
        return Math.min(left + right, calculator.getLimit());
    }

    public int getLimit() {
        return limit;
    }
}

And the test becomes:

    @Test
    @DisplayName("Ensure that adding numbers DOES work correctly.")
    void addition() throws Exception {
        // ARRANGE.
        Calculator calculator = Mockito.mock(Calculator.class);

        Mockito.when(calculator.getLimit()).thenReturn(100);

        // ACT.
        int result = Calculator.add(calculator, 1, 1);

        // ASSERT.
        Assertions.assertEquals(3, result);
    }

But in this case, I prefer to create a new Class called ArithmeticCalculator with a simple constructor and use it inside the Calculator class (Aka composition ), then redirect the arithmetic operations to it, so it will help for a better testing and it may promote a Single Responsibility Principle .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM