如何处理模拟对象的方差?

时间:2014-02-25 23:35:17

标签: java unit-testing junit mocking

假设我有以下想要模拟的界面:

Searcher.java

public interface Searcher {

    public String search();

    public void someMethod();

}

当我想为此界面使用不同的模拟实现时,我该怎么办?例如,在一个测试中,我想让search()方法返回空字符串,在另一个测试中我希望它开始做一些HTTP请求等。

我是否封装了行为,就像fx一样。将它放在SearchBehaviour接口中,然后为该接口编写实现:

public class SearcherMock implements Searcher {

    private SearchBehaviour searchBehaviour;

    public SearcherMock(SearchBehaviour searchBehaviour) {
        this.searchBehaviour = searchBehaviour;
    }

    @Override
    public String search() {
        return searchBehaviour.search();
    }

    @Override
    public void someMethod() {
        // Do something here
    }

} 

或者我是否为每个模拟实现创建一个新的模拟类? FX。 EmptySearcherHTTPSearcher

3 个答案:

答案 0 :(得分:3)

我建议您使用java模拟工具,例如jMockMockito,这样可以节省您一些时间,不让您自己编写模型工具,而是编写好的模型:)

使用Mockito你可以做这样的事情(虽然没有经过测试):

import static org.mockito.Mockito.*;

Searcher mockedEmptySearcher = mock(Searcher.class);

// define how empty searcher should behave
when(mockedList.search()).thenReturn("");

答案 1 :(得分:2)

请记住,您可以在不使用库的情况下为不同的测试自由创建匿名内部类:

@Test public void test1() {
  // When referring to outside local variables, they must be final.
  final AtomicBoolean someMethodCalled = new AtomicBoolean(false);

  Searcher fakeSearcher = new Searcher() {
    @Override public String search() {
      return "stubbed return value";
    }

    @Override public void someMethod() {
      someMethodCalled.set(true);
    }       
  };
  SystemUnderTest systemUnderTest = new SystemUnderTest(fakeSearcher);
  systemUnderTest.pressBigRedButton();
  assertTrue("someMethod should have been called", someMethodCalled.get());
}

甚至变得非常聪明:

private Searcher createFakeSearcher(final String... searchResults) {
  return new Searcher() {
    int returnIndex = 0;

    @Override public String search() {
      return searchResults[returnIndex++];
    }

    @Override public void someMethod() {}       
  };
}

但是一些时间投入学习一个模拟框架将很好地为你和你的测试服务,因为像Mockito这样的框架旨在剥离那个样板:

// Uses static imports from org.mockito.Mockito;
@Test public void test1() {
  Searcher mockSearcher = mock(Searcher.class);
  when(mockSearcher.search())
      .thenReturn("search one")
      .thenReturn("search two")
      .thenThrow(new IllegalStateException());

  SystemUnderTest systemUnderTest = new SystemUnderTest(mockSearcher);
  systemUnderTest.pressBigRedButton();
  verify(mockSearcher, times(2)).someMethod();
}

为了更好地概念性地介绍测试双打(假人/存根/模拟/假货)以及它们之间的差异,请阅读Martin Fowler's article here或直接进入Mockito documentation

答案 2 :(得分:2)

您可以使用像Mockito这样的模拟框架,并使用JUnitParams参数化您的测试。

假设您有一个验证Searcher的类,如果搜索返回'valid',它将通过。代码和测试如下所示,有2个测试用例,1个用于有效,1个用于无效的搜索结果。

public class SomeClass {
    public boolean isValid(Searcher searcher) {
        return searcher.search().equals("valid");
    }
}

@RunWith(JUnitParamsRunner.class)
public class SomeClassTest {

    public Object[] provideIsValid() {
        return new Object[]{
                new Object[]{ "invalid", false },
                new Object[]{ "valid", true }
        };
    }

    @Test
    @Parameters(method = "provideIsValid")
    public void testIsValid(String output, String expected) {
        SomeClass someClass = new SomeClass();
        Searcher mock = mock(Searcher.class);
        when(mock.search()).thenReturn(output);

        String actual = someClass.isValid(mock);
        assertEquals(expected, actual);
    }