Mock测试技能可以或许制止你为了测试一个要领,却需要自行构建整个依赖干系的事情,昆山软件开发,而且可以或许让你专注于当前被测试工具的逻辑,而不是其依赖的其他工具的逻辑。
举例来说,好比你需要测试Foo.methodA,而这个要领依赖了Bar.methodB,又通报依赖到了Zoo.methodC,于是它们的依赖干系就是Foo->Bar->Zoo,所以在测试代码里你必需自行new Bar和Zoo。
有人会说:”我直接用Spring的DI机制不就行了吗?”简直,你可以用Spring的DI机制,不外办理不了测试代码耦合渡过高的问题:
因为Foo要领内部挪用了Bar和Zoo的要领,所以你对其做单位测试的时候,必需完全相识Bar和Zoo要领的内部逻辑,而且审慎的传参和assert功效,一旦Bar和Zoo的代码修改了,你的Foo测试代码很大概就会运行失败。
所以这个时候我们需要一种机制,能过让我们在测试Foo的时候不依赖于Bar和Zoo的详细实现,即不体贴其内部逻辑,只存眷Foo内部的逻辑,从而将Foo的每个逻辑分支都测试到。
所以业界就发生了Mock技能,它可以让我们做一个假的Bar(不需要Zoo,因为只有真的Bar才需要Zoo),然后节制这个假的Bar的行为(让它返回什么就返回什么),昆山软件公司,以此来测试Foo的每个逻辑分支。
你必定会问,这样的测试有意义吗?在真实情况里Foo用的是真的Bar而不是假的Bar,你用假的Bar测试乐成能代表真实情况不出问题?
其实假Bar代表的是一个行为正确的Bar,用它来测试就能验证”在Bar行为正确的环境下Foo的行为是否正确”,而真Bar的行为是否正确会由它本身的测试代码来验证。
Mock技能的另一个长处是可以或许让你只管制止集成测试,好比我们可以Mock一个Repository(数据库操纵类),让我们只管多写单位测试,提高测试代码执行效率。
spring-boot-starter-test依赖了Mockito,所以我们会在本章里利用Mockito来讲授。
被测试类
先先容一下接下来要被我们测试的类Foo、Bar俩兄弟。
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; } }
例子1: 不利用Mock技能
源代码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:
这两种方法都不是很优雅,看下面利用Mockito的例子。
例子2:利用Mockito
源代码[MockitoTest][src-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); } }
例子3:共同Spring Test
源代码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)。
例子4:共同Spring Test(多层依赖)
当Bean存在这种依赖干系其时候:LooImpl -> FooImpl -> Bar,我们应该怎么测试呢?
凭据Mock测试的原则,这个时候我们应该mock一个Foo工具,把这个注入到LooImpl工具里,就像例子3里的一样。
不外假如你不想mock Foo而是想mock Bar的时候,其实做法和前面也差不多,Spring会自动将mock Bar注入到FooImpl中,然后将FooImpl注入到LooImpl中。