Github地址html
Mock测试技术可以避免你为了测试一个方法,却须要自行构建整个依赖关系的工做,而且可以让你专一于当前被测试对象的逻辑,而不是其依赖的其余对象的逻辑。java
举例来讲,好比你须要测试Foo.methodA
,而这个方法依赖了Bar.methodB
,又传递依赖到了Zoo.methodC
,因而它们的依赖关系就是Foo->Bar->Zoo
,因此在测试代码里你必须自行new Bar和Zoo。git
有人会说:"我直接用Spring的DI机制不就好了吗?"的确,你能够用Spring的DI机制,不过解决不了测试代码耦合度太高的问题:github
由于Foo方法内部调用了Bar和Zoo的方法,因此你对其作单元测试的时候,必须彻底了解Bar和Zoo方法的内部逻辑,而且谨慎的传参和assert结果,一旦Bar和Zoo的代码修改了,你的Foo测试代码极可能就会运行失败。spring
因此这个时候咱们须要一种机制,能过让咱们在测试Foo的时候不依赖于Bar和Zoo的具体实现,即不关心其内部逻辑,只关注Foo内部的逻辑,从而将Foo的每一个逻辑分支都测试到。数据库
因此业界就产生了Mock技术,它可让咱们作一个假的Bar(不须要Zoo,由于只有真的Bar才须要Zoo),而后控制这个假的Bar的行为(让它返回什么就返回什么),以此来测试Foo的每一个逻辑分支。ide
你确定会问,这样的测试有意义吗?在真实环境里Foo用的是真的Bar而不是假的Bar,你用假的Bar测试成功能表明真实环境不出问题?spring-boot
其实假Bar表明的是一个行为正确的Bar,用它来测试就能验证"在Bar行为正确的状况下Foo的行为是否正确",而真Bar的行为是否正确会由它本身的测试代码来验证。单元测试
Mock技术的另外一个好处是可以让你尽可能避免集成测试,好比咱们能够Mock一个Repository(数据库操做类),让咱们尽可能多写单元测试,提升测试代码执行效率。测试
spring-boot-starter-test
依赖了Mockito,因此咱们会在本章里使用Mockito来说解。
public interface Foo { boolean checkCodeDuplicate(String code); } public interface Bar { Set<String> getAllCodes(); } @Component public class FooImpl implements Foo { private Bar bar; @Override public boolean checkCodeDuplicate(String code) { return bar.getAllCodes().contains(code); } @Autowired public void setBar(Bar bar) { this.bar = bar; } }
源代码NoMockTest:
public class NoMockTest { @Test public void testCheckCodeDuplicate1() throws Exception { FooImpl foo = new FooImpl(); foo.setBar(new Bar() { @Override public Set<String> getAllCodes() { return Collections.singleton("123"); } }); assertEquals(foo.checkCodeDuplicate("123"), true); } @Test public void testCheckCodeDuplicate2() throws Exception { FooImpl foo = new FooImpl(); foo.setBar(new FakeBar(Collections.singleton("123"))); assertEquals(foo.checkCodeDuplicate("123"), true); } public class FakeBar implements Bar { private final Set<String> codes; public FakeBar(Set<String> codes) { this.codes = codes; } @Override public Set<String> getAllCodes() { return codes; } } }
这个测试代码里用到了两种方法来作假的Bar:
匿名内部类
作了一个FakeBar
这两种方式都不是很优雅,看下面使用Mockito的例子。
源代码MockitoTest:
public class MockitoTest { @Mock private Bar bar; @InjectMocks private FooImpl foo; @BeforeMethod(alwaysRun = true) public void initMock() { MockitoAnnotations.initMocks(this); } @Test public void testCheckCodeDuplicate() throws Exception { when(bar.getAllCodes()).thenReturn(Collections.singleton("123")); assertEquals(foo.checkCodeDuplicate("123"), true); } }
咱们先给了一个Bar的Mock实现:@Mock private Bar bar;
而后又规定了getAllCodes
方法的返回值:when(bar.getAllCodes()).thenReturn(Collections.singleton("123"))
。这样就把一个假的Bar定义好了。
最后利用Mockito把Bar注入到Foo里面,@InjectMocks private FooImpl foo;
、MockitoAnnotations.initMocks(this);
源代码Spring_1_Test:
@ContextConfiguration(classes = FooImpl.class) @TestExecutionListeners(listeners = MockitoTestExecutionListener.class) public class Spring_1_Test extends AbstractTestNGSpringContextTests { @MockBean private Bar bar; @Autowired private Foo foo; @Test public void testCheckCodeDuplicate() throws Exception { when(bar.getAllCodes()).thenReturn(Collections.singleton("123")); assertEquals(foo.checkCodeDuplicate("123"), true); } }
要注意,若是要启用Spring和Mockito,必须添加这么一行:@TestExecutionListeners(listeners = MockitoTestExecutionListener.class)
。
当Bean存在这种依赖关系当时候:LooImpl -> FooImpl -> Bar
,咱们应该怎么测试呢?
按照Mock测试的原则,这个时候咱们应该mock一个Foo
对象,把这个注入到LooImpl
对象里,就像例子3里的同样。
不过若是你不想mock Foo
而是想mock Bar
的时候,其实作法和前面也差很少,Spring会自动将mock Bar注入到FooImpl
中,而后将FooImpl
注入到LooImpl
中。
源代码Spring_2_Test:
@ContextConfiguration(classes = { FooImpl.class, LooImpl.class }) @TestExecutionListeners(listeners = MockitoTestExecutionListener.class) public class Spring_2_Test extends AbstractTestNGSpringContextTests { @MockBean private Bar bar; @Autowired private Loo loo; @Test public void testCheckCodeDuplicate() throws Exception { when(bar.getAllCodes()).thenReturn(Collections.singleton("123")); assertEquals(loo.checkCodeDuplicate("123"), true); } }
也就是说,得益于Spring Test Framework,咱们可以很方便地对依赖关系中任意层级的任意Bean作mock。
源代码Boot_1_Test:
@SpringBoot_1_Test(classes = { FooImpl.class }) @TestExecutionListeners(listeners = MockitoTestExecutionListener.class) public class Boot_1_Test extends AbstractTestNGSpringContextTests { @MockBean private Bar bar; @Autowired private Foo foo; @Test public void testCheckCodeDuplicate() throws Exception { when(bar.getAllCodes()).thenReturn(Collections.singleton("123")); assertEquals(foo.checkCodeDuplicate("123"), true); } }