我已经在使用Java遗留代码工作几个月了,以下是我正在处理的一些问题:
- 测试覆盖率为0%。
- 有很多庞大的函数,有时甚至会看到超过300行的代码。
- 有很多私有方法和静态方法。
- 代码高度耦合。
一开始我非常困惑,发现在遗留代码中使用TDD很困难。经过数周的kata训练和单元测试以及模拟技能的实践,我的恐惧感降低了,我感觉更加自信了。最近我发现了一本书叫做:有效地处理遗留代码,我没有读过它,只是查看了目录,发现了一些对我来说新的东西,即“接缝”。显然,在处理遗留代码时,这非常重要。
我认为这些接缝可以帮助我打破依赖关系,使我的代码可测试,从而增加代码覆盖率并使我的单元测试更加精确。
但我有很多疑问:
- 有人能解释一下接缝和模拟之间的区别吗?
- 在不测试之前,接缝是否违反了TDD规则中不触及生产代码的原则?
- 你能给我展示一个简单的例子来比较接缝和模拟吗?
下面我想粘贴一个我今天做的例子,试图打破依赖关系,以达到使代码可测试和最终增加测试覆盖率的目的。如果您看到任何错误,请评论一下,谢谢!
这就是遗留代码在开始时的样子:
public class ABitOfLegacy
{
private String sampleTitle;
String output;
public void doSomeProcessing(HttpServletRequest request) {
String [] values = request.getParameterValues(sampleTitle);
if (values != null && values.length > 0)
{
output = sampleTitle + new Date().toString() + values[0];
}
}
}
如果我只是添加一个单元测试来调用该方法并断言变量输出在调用后具有某个值,则我将犯错,因为我不是在进行单元测试,而是在进行集成测试。所以我需要做的是摆脱参数中的依赖关系。为了这样做,我将参数替换为一个接口:
public class ABitOfLegacy
{
private String sampleTitle;
String output;
public void doSomeProcessing(ParameterSource request) {
String [] values = request.getParameters(sampleTitle);
if (values != null && values.length > 0)
{
output = sampleTitle + new Date().toString() + values[0];
}
}
}
这是接口的外观:
public interface ParameterSource {
String[] getParameters(String name);
}
接下来我做的事情是创建自己的接口实现,但我将HttpServletRequest作为全局变量包含,并使用HttpServletRequest的方法/方法实现接口的方法:
public class HttpServletRequestParameterSource implements ParameterSource {
private HttpServletRequest request;
public HttpServletRequestParameterSource(HttpServletRequest request) {
this.request = request;
}
public String[] getParameters(String name) {
return request.getParameterValues(name);
}
}
到目前为止,我认为对生产代码所做的所有修改都是安全的。 现在我在我的测试包中创建了Seam。如果我理解正确,现在我可以安全地更改Seam的行为。这是我做的方式:
public class FakeParameterSource implements ParameterSource {
public String[] values = {"ParamA","ParamB","ParamC"};
public String[] getParameters(String name) {
return values;
}
}
最后一步是从Seam获取支持,以测试该方法的原始行为。
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import code.ABitOfLegacyRefactored;
import static org.hamcrest.Matchers.*;
public class ABitOfLegacySpecification {
private ABitOfLegacy aBitOfLegacy;
private String EMPTY = null;
@Before
public void initialize() {
aBitOfLegacy = new ABitOfLegacy();
}
@Test
public void
the_output_gets_populated_when_the_request_is_not_empty
() {
FakeParameterSource fakeParameterSource = new FakeParameterSource();
aBitOfLegacy.doSomeProcessing(fakeParameterSource);
assertThat(aBitOfLegacy.output,not(EMPTY));
}
@Test(expected=NullPointerException.class)
public void
should_throw_an_exception_if_the_request_is_null
() {
aBitOfLegacy.doSomeProcessing(null);
}
}
这将为我提供100%的测试覆盖率。 感谢您的意见:
- 我是否正确地断开了依赖关系?
- 单元测试有什么遗漏吗?
- 有什么可以做得更好的吗?
- 这个例子足以帮助我理解Seam和Mock之间的区别吗?
- 如果我不使用Seam,Mock如何帮助我?