重构遗留代码所需的建议
Advice needed for refactoring legacy code
我正在处理遗留代码库,我会使用 TDD 向我当前正在更改的代码添加新功能。
请注意,当前代码库没有任何 UT。
我有一个 Calculator
class 具有以下实现:
public final class Calculator extends CalculatorBase {
public Calculator(Document document) throws Exception {
super(document);
}
public int Multiply(int source, int factor) {
return source * factor;
}
}
此 class 继承自以下基础 class:
public class CalculatorBase {
public CalculatorBase(Document document) throws Exception {
throw new Exception("UNAVAILABLE IN UT CONTEXT.");
}
}
注意:构造函数实际上做了很多事情,我不想在 UT 中做这些事情。
为简单起见,我让构造函数抛出异常。
现在我想向计算器 class 添加一个 'Add' 函数。
这个函数看起来像:
public int Add(int left, int right) {
return left + right;
}
这段特定代码的 UT 应该非常简单。
@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);
}
由于 CalculatorBase
基类的构造函数确实抛出异常,此单元测试将永远不会通过。
使这个可测试的棘手部分是 CalculatorBase
class 是由工具自动生成的,因此无法修改 class 的源代码。
我应该采取哪些(基本)步骤来确保可以测试 Calculator
class 上的 Add
方法?目标是使整个项目可测试,甚至摆脱自动生成的东西,但我想尽可能使用 TDD,以便逐步重构代码。
有人可能会争辩说我可以将 Add
方法设为静态,因为它不使用 Calculator
class 的任何依赖项,但代码只是快速添加在一起。在实际场景中,Add
函数是其他确实消耗 Calculator
class.
状态的东西
您可以:
- 将新方法创建为静态方法
- 创建一个临时替代构造函数,注释为 "Only for testing"
- 重构 class 以移除依赖项
- 用PowerMock
抑制它
但最安全的方法是创建脚手架测试。即使构造函数具有依赖性,这也会构建 class 。您可能需要使用 setter 来打破封装以进行测试。一旦 class 真正得到很好的测试,您就可以重构 class,添加更好的测试,最后删除脏脚手架测试。根据 class 的复杂性,这可能是适当的或矫枉过正。
您可以创建一个新的 protected 或 package-private 静态方法 add
以及额外的 Calculator
参数(至少现在,直到可以轻松实例化 class),然后使用 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;
}
}
测试变为:
@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);
}
但在这种情况下,我更喜欢用一个简单的构造函数创建一个名为 ArithmeticCalculator
的新 Class,并在 Calculator
class(又名 composition), then redirect the arithmetic operations to it, so it will help for a better testing and it may promote a Single Responsibility Principle.
我正在处理遗留代码库,我会使用 TDD 向我当前正在更改的代码添加新功能。
请注意,当前代码库没有任何 UT。
我有一个 Calculator
class 具有以下实现:
public final class Calculator extends CalculatorBase {
public Calculator(Document document) throws Exception {
super(document);
}
public int Multiply(int source, int factor) {
return source * factor;
}
}
此 class 继承自以下基础 class:
public class CalculatorBase {
public CalculatorBase(Document document) throws Exception {
throw new Exception("UNAVAILABLE IN UT CONTEXT.");
}
}
注意:构造函数实际上做了很多事情,我不想在 UT 中做这些事情。 为简单起见,我让构造函数抛出异常。
现在我想向计算器 class 添加一个 'Add' 函数。 这个函数看起来像:
public int Add(int left, int right) {
return left + right;
}
这段特定代码的 UT 应该非常简单。
@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);
}
由于 CalculatorBase
基类的构造函数确实抛出异常,此单元测试将永远不会通过。
使这个可测试的棘手部分是 CalculatorBase
class 是由工具自动生成的,因此无法修改 class 的源代码。
我应该采取哪些(基本)步骤来确保可以测试 Calculator
class 上的 Add
方法?目标是使整个项目可测试,甚至摆脱自动生成的东西,但我想尽可能使用 TDD,以便逐步重构代码。
有人可能会争辩说我可以将 Add
方法设为静态,因为它不使用 Calculator
class 的任何依赖项,但代码只是快速添加在一起。在实际场景中,Add
函数是其他确实消耗 Calculator
class.
您可以:
- 将新方法创建为静态方法
- 创建一个临时替代构造函数,注释为 "Only for testing"
- 重构 class 以移除依赖项
- 用PowerMock 抑制它
但最安全的方法是创建脚手架测试。即使构造函数具有依赖性,这也会构建 class 。您可能需要使用 setter 来打破封装以进行测试。一旦 class 真正得到很好的测试,您就可以重构 class,添加更好的测试,最后删除脏脚手架测试。根据 class 的复杂性,这可能是适当的或矫枉过正。
您可以创建一个新的 protected 或 package-private 静态方法 add
以及额外的 Calculator
参数(至少现在,直到可以轻松实例化 class),然后使用 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;
}
}
测试变为:
@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);
}
但在这种情况下,我更喜欢用一个简单的构造函数创建一个名为 ArithmeticCalculator
的新 Class,并在 Calculator
class(又名 composition), then redirect the arithmetic operations to it, so it will help for a better testing and it may promote a Single Responsibility Principle.