我正在使用旧版代码库,我将使用TDD向当前正在更改的代码中添加新功能。
请注意,当前代码库没有任何UT。
我有一个Calculator
类,具有以下实现:
public final class Calculator extends CalculatorBase {
public Calculator(Document document) throws Exception {
super(document);
}
public int Multiply(int source, int factor) {
return source * factor;
}
}
该类继承自以下基类:
public class CalculatorBase {
public CalculatorBase(Document document) throws Exception {
throw new Exception("UNAVAILABLE IN UT CONTEXT.");
}
}
注意:构造函数实际上做了很多事情,我不想在UT中做。为简单起见,我使构造函数抛出异常。
现在,我想向Calculator类添加'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
类是由工具自动生成的,因此无法修改该类的源代码。
我应该采取哪些步骤(婴儿)以确保可以测试Add
类上的Calculator
方法?目的是使整个项目可测试,甚至摆脱自动生成的内容,但我想尽可能使用TDD以便逐渐重构代码。
有人可能会争辩说我可以使Add
方法静态化,因为它不使用Calculator
类的任何依赖关系,但是代码只是快速地添加在一起。在实际情况下,Add
函数是其他确实消耗Calculator
类状态的函数。
您可以:
但是最安全的方法是创建脚手架测试。即使构造函数具有依赖关系,也可以构建类。您可能需要为了测试而用setter破坏封装。一旦对课程进行了充分的测试,您就可以重构课程,添加更好的测试,最后删除脏的脚手架测试。根据班级的复杂程度,这可能是适当的,也可能是过大的。
如果我理解正确,那么这里的一个窍门是使用无害的构造函数将测试的类子类化:
public final class TestCalculator extends Calculator {
public TestCalculator(Document document) {
// No call to super!
// You might need to replicate some logic here, e.g. setting dependencies
// needed for code you want to test
}
public int Multiply(int source, int factor) {
return source * factor;
}
}
在此示例中,由于add
方法是无状态的,什么也不做的构造函数可以很好地工作,但您的实际方法可能更复杂。此测试将通过:]
@Test
@DisplayName("Ensure that adding numbers DOES work correctly.")
void addition() throws Exception {
// ARRANGE.
Calculator calculator = new TestCalculator(null);
// ACT.
int result = calculator.Add(1, 1);
// ASSERT.
Assertions.assertEquals(2, result);
}
编辑:好的,因此您的评论清楚表明您的实际系统要复杂一些。让我们使该示例更为实际。我将从您的构造函数参数为Document
中获得启发,并假设您的CalculatorBase记录了它所做的所有和的记录。我还假设您需要逻辑来设置字体,然后才能对其进行写入。因此您的基类如下所示:
public final class CalculatorBase {
private final Document document;
public CalculatorBase(Document document) throws Exception {
this.document = document;
document.setFont('Times New Roman');
// this symbolises all the logic you don't want
throw new Exception("UNAVAILABLE IN UT CONTEXT.");
}
public add(int x, int y) {
int result = x + y;
document.write(x + " + " y + " = " + result);
return result;
}
}
因此,此基类没有默认的构造函数,现在您要测试的add
方法期望文档存在字体集。
如果您真正使用文档并正确设置字体,则您的测试类将具有构造函数:
public TestCalculator(Document document) {
this.document = document;
document.setFont('Times New Roman'); // just remove the exception
}
[如果只关心告诉构造函数中传递的文档使用正确的参数来调用write
方法,则可以使构造函数更简单:
public TestCalculator(Document document) {
this.document = document;
}
并使用Mockito模拟来验证副作用:
import static org.mockito.Mockito.*;
@Test
@DisplayName("Ensure that adding numbers DOES work correctly.")
void addition() throws Exception {
// ARRANGE.
Document mockDocument = mock(Document.class)
Calculator calculator = new TestCalculator(mockDocument);
// ACT.
int result = calculator.Add(1, 1);
// ASSERT.
Assertions.assertEquals(2, result);
verify(mockDocument).write("1 + 1 = 2");
}