接缝和模拟有什么区别?
自从我使用java遗留代码以来已经几个月了,这是我正在处理的一些事情:
- 0% 测试覆盖率。
- 在我甚至看到一些超过300行代码的场合,巨大的功能。
- 许多私有方法和场合静态方法。
- 高度紧密耦合的代码。
一开始我非常困惑,我发现很难在遗产中使用TDD。在做了几个星期的katas并练习了我的单元测试和模拟技能之后,我的恐惧减少了,我感到更加自信。最近,我发现了一本书,叫做:有效地处理遗产,我没有读过它,我只是看了一下目录,我发现了一些对我来说很新的东西,The Seams。显然,在遗产中工作时,这一点非常重要。
我认为这个Seams可以帮助我打破依赖关系,使我的代码可测试,这样我就可以增加代码覆盖率,使我的单元测试更加精确。
但我有很多疑问:
- 有人能解释一下接缝和模拟之间的区别吗?
- 在测试之前,接缝,打破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。如果我理解得很好,现在我能够安全地改变接缝的行为。我是这样做的:
public class FakeParameterSource implements ParameterSource {
public String[] values = {"ParamA","ParamB","ParamC"};
public String[] getParameters(String name) {
return values;
}
}
最后一步,将是获得接缝的支持,以测试该方法的原始行为。
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%的测试覆盖率。我感谢您的想法:
- 我是否正确打破了依赖关系?
- 单元测试是否缺少某些内容?
- 还有什么可以做得更好?
- 这个例子是否足以帮助我理解接缝和模拟之间的区别?
- 如果我不使用接缝,模拟怎么能在这里帮助我?