2013-03-10 44 views
6

它是几个月,因为我用java遗留代码的工作,这是一些我处理的事情:Seam和Mock有什么区别?

  • 0%的测试覆盖率。
  • 巨大的功能,我甚至看到一些超过300行的代码。
  • 很多私人方法和场合静态方法。
  • 高度紧密耦合的代码。

刚开始时我很困惑,我发现很难在传统中使用TDD。做了几个星期的katas并练习我的单元测试和嘲讽技能后,我的恐惧减少了,我感到更自信一些。最近我发现了一本名为working effectivelly with legacy的书,我没有读它,我只是看了目录,然后发现了一些对我而言是新的东西,The Seams。显然这在遗产工作中非常重要。

我认为这个Seams可以帮助我打破依赖关系,使我的代码可测试,因此我可以增加代码覆盖率并使我的单元测试更加精确。

但我有很多疑惑:

  • 有人可以解释我的接缝和模拟之间的区别?
  • 做测试之前,Do Seams打破了TDD规则中关于未触及生产代码的规定吗?
  • 你能告诉我一些简单的例子来比较一下Seam和Mock吗?

下面我想粘贴一个我今天做过的例子,我试图打破依赖关系,使代码可测试并最终增加测试覆盖率。如果您看到一些错误,您可以评论一下,我将不胜感激?

这是遗留代码怎么看起来像开头:

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的方法/ s的实现接口的方法:

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; 
    } 
} 

最后的一步,将是来自Seam获得支持,测试方法的原始behavoir。

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,模拟器如何帮助我?
+0

嘲笑不是煤层的替代品。 – Gishu 2013-03-11 10:23:38

+1

你应该拿到那本书并阅读它。 – 2013-03-11 11:19:02

回答

4

接缝

A seam是一个地方,可以让你修改行为而不需要修改代码。

在你的例子中,下面是一个对象接缝的例子(如果我没有弄错)。它允许您传递不同的对象而无需更改代码。因此它是一种接缝。

public void doSomeProcessing(ParameterSource request) {..} 

通过使参数为抽象类型(而不是具体类),您已经引入了接缝。接缝现在允许您在不修改代码的情况下修改方法的行为 - 即在调用的地方,我可以传入不同的对象并使该方法执行其他操作。

嘲笑

现在不是创建自定义的假(创建接口的子类型),你可以使用模拟框架,做这样的事情

嘲弄还支持主张的具体方法是否呼吁它,参数匹配和其他漂亮的功能,以供测试使用。更少的测试代码来维护。 Mock主要用于声明正在对某个依赖项进行特定调用。在你的例子中,你似乎需要一个存根,你只想返回一个罐头值。

原谅我的生锈JMock的..

@Test 
    public void 
    the_output_does_not_get_populated_when_the_request_is_empty 
    () { 
     Mockery context = new Mockery(); 
     final ParameterSource mockSource = context.mock(ParameterSource.class) 

context.checking(new Expectations(){{ 
    oneOf(mockSource).getParameters(); 
      will(returnValue(new string[]{"ParamA","ParamB","ParamC"}); 
}}); 
     aBitOfLegacy.populate(mockSource); 
     assertThat(aBitOfLegacy.output,not(EMPTY)); 
    } 

在.net

var mockSource = new Mock<ParameterSource>(); 
mockSource.Setup(src => src.GetParameters()) 
      .Returns(new []{"ParamA","ParamB","ParamC"}); 
+0

我还是有点困惑。你认为,我的上面的例子可以使用模拟而不是接缝进行测试吗?有了现代框架,我们可以训练嘲笑以某种方式表现。另外,我们还要进行测试,接缝会难以维持吗? – sfrj 2013-03-11 23:49:27

+0

糟糕 - 我的帖子有点混乱。更新了一个mocks的例子...可能不会编译 - java对我来说不是原生的。要使用模拟,你仍然需要一个接缝 - 通常是一个对象接缝作为一个参数或参数。没有“改为” - 接缝允许您替换不同的对象(例如,用于测试)。 Mocks简化了交互测试。它们节省了创建用于测试和维护它们的自定义假对象的工作量。他们还提供更好的报告。 – Gishu 2013-03-12 05:33:20

+0

我明白你的解释。感谢这个测试例子很好:)直到现在我还没有明白Seams何时重要,单元测试。 – sfrj 2013-03-12 17:08:07

7

接缝是,你可以在行为插入修改代码的地方。您在设置注入依赖关系时创建了接缝。

利用接缝的一种方法是插入某种假物。 Fake's可以像您的示例那样手动滚动,或者使用Mockito等工具创建。

所以,一个模拟是一种假冒,而一个假冒常常是利用一个Seam来利用的。

至于你的测试和你打破依赖的方式,这几乎是我会做到这一点。

+0

很好的解释,简短但准确。谢谢。 +1 – sfrj 2013-03-12 17:09:28