Java 如何处理模拟对象中的差异?
假设我有以下要模拟的界面: Searcher.javaJava 如何处理模拟对象中的差异?,java,unit-testing,junit,mocking,Java,Unit Testing,Junit,Mocking,假设我有以下要模拟的界面: Searcher.java public interface Searcher { public String search(); public void someMethod(); } 当我想为这个接口使用不同的模拟实现时,我该怎么做?例如,在一个测试中,我希望让search()方法返回空字符串,在另一个测试中,我希望它开始执行一些HTTP请求等 我是否封装了行为,比如fx。将其放入SearchBehavior接口,然后为该接口编写实现: p
public interface Searcher {
public String search();
public void someMethod();
}
当我想为这个接口使用不同的模拟实现时,我该怎么做?例如,在一个测试中,我希望让search()
方法返回空字符串,在另一个测试中,我希望它开始执行一些HTTP请求等
我是否封装了行为,比如fx。将其放入SearchBehavior接口,然后为该接口编写实现:
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
}
}
还是为每个不同的模拟实现创建一个新的模拟类?外汇
EmptySearcher
和HTTPSearcher
?我建议您使用java模拟工具,例如or,它不会让您自己编写模拟工具,而是编写好的模拟,从而节省您的时间:)
使用Mockito,您可以执行以下操作(但未经测试):
我建议您使用诸如或之类的java模拟工具,这样可以节省一些时间,因为不允许您自己编写模拟工具,而是编写好的模拟:) 使用Mockito,您可以执行以下操作(但未经测试):
请记住,您可以为不同的测试自由创建匿名内部类,而无需使用库:
@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();
}
要更好地从概念上介绍双重测试(Dummie/stubs/Mock/fakes)以及它们之间的差异,请阅读或深入阅读。请记住,您可以在不使用库的情况下为不同的测试自由创建匿名内部类:
@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();
}
要更好地从概念上介绍双重测试(假人/存根/模拟/假货)以及它们之间的区别,请阅读或深入阅读。您可以使用类似Mockito的模拟框架,并使用JUnitParams参数化您的测试 假设您有一个验证Searcher的类,如果search返回“valid”,它将通过。代码和测试如下所示,有两个测试用例,一个用于有效搜索结果,一个用于无效搜索结果
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);
}
您可以使用类似Mockito的模拟框架,并使用JUnitParams参数化您的测试 假设您有一个验证Searcher的类,如果search返回“valid”,它将通过。代码和测试如下所示,有两个测试用例,一个用于有效搜索结果,一个用于无效搜索结果
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);
}
单元测试的HTTP请求?考虑到您的接口(不会引发任何类型的异常;如果请求失败怎么办?),听起来很可疑。不管HTTP部分是什么,它只是用于说明差异,我认为您不想这样做;真正的模拟不会运行复杂的代码。您将让它返回您想要的值,以便可以测试该模拟的用户。也就是说,使用mockito,你会在(searcher.search())时执行
,然后返回(“某物”)代码>.HTTP请求进行单元测试?考虑到您的接口(不会引发任何类型的异常;如果请求失败怎么办?),听起来很可疑。不管HTTP部分是什么,它只是用于说明差异,我认为您不想这样做;真正的模拟不会运行复杂的代码。您将让它返回您想要的值,以便可以测试该模拟的用户。也就是说,使用mockito,你会在(searcher.search())时执行,然后返回(“某物”)
.OP,请注意,JUnit4有一个内置的运行程序,但提供了一些其他特性。这两种方法都适用于您所询问的变体。请注意,JUnit4有一个内置的runner,但提供了更多的功能。这两种方法都适用于您所询问的变体。