单元测试是项目的重要组成部分。尤其是对持续发展的产品,单元测试在后期的维护,回归有重要等方面有重要作用。
? 这样代码在项目中随处可见,看看我们应该如何测试
?
?
public class NotifyService { private UserCenter uc; private MessageCenter mc; public void sendMessage(long userId, String message) { String email = uc.getUser(userId).getEmail(); mc.sendEmail(email, message); } public void setUc(UserCenter uc) { this.uc = uc; } public void setMc(MessageCenter mc) { this.mc = mc; } }
UserCenter和MessageCenter都是接口,User是一个简单的JavaBean
?
由于uc和mc乃外部依赖,此类不需也不应保证uc和mc的正确性,此类只需保证:
? 假设uc和mc是正确的,那么我也是正确的。
所以需要隔离依赖--使用mock
?
如果使用EasyMock,此类之单元测试或许如下
?
?
public class EasyMockNotifyServiceTest { private NotifyService notifyService; private UserCenter uc; private MessageCenter mc; @Before public void setUp() { notifyService = new NotifyService(); uc = createMock(UserCenter.class); mc = createMock(MessageCenter.class); notifyService.setUc(uc); notifyService.setMc(mc); } @Test public void testSendMessage() { Long id = 1L; String email = "foo@bar"; String message = "hello"; expect(uc.getUser(id)).andReturn(createUserWithEmail(email)); mc.sendEmail(eq(email), eq(message)); replay(uc); replay(mc); notifyService.sendMessage(id, message); verify(mc);//verify a mocked behavior } private User createUserWithEmail(String email) { User user = new User(); user.setEmail(email); return user; } }?
?
? ?当然,此测试并不充分,easymock需要mock每个依赖,对mock的所有方法调用作expect,然后验证需要验证的行为。
?
?
?
mc.sendEmail(eq(email), eq(message));
?
? ? 此处代码理解起来有些怪异,实际上,此处的语义应该是 expect mc.sendEmail... called,即方法执行完毕后sendMail必以正确的参数调用,奈何java 泛型中并未覆盖void型,所以通常会在mock行为调用之后
加上此行以明确语义
expectLastCall().times(1);
?
? ? 另外一个值得注意的地方
?
verify(mc);//verify a mocked behavior
?
? ? 并未验证uc,我的想法是对于uc我们需要它提供数据(桩),而不需要验证其行为。
?
? ? 假使使用Mockito,单元测试也许是这个样子的
?
public class NotifyServiceTest { private NotifyService notifyService; private UserCenter uc; private MessageCenter mc; @Before public void setUp() { notifyService = new NotifyService(); uc = mock(UserCenter.class); mc = mock(MessageCenter.class); notifyService.setUc(uc); notifyService.setMc(mc); } @Test public void testSendMessage() { long userId = 1L; String email = "foo@bar"; when(uc.getUser(userId)).thenReturn(createUserWithEmail(email)); notifyService.sendMessage(userId, "hello"); verify(mc).sendEmail(eq(email), eq("hello")); } private User createUserWithEmail(String email) { User user = new User(); user.setEmail(email); return user; } }?
看到testSendMessage方法
?
@Test public void testSendMessage() { long userId = 1L; String email = "foo@bar"; when(uc.getUser(userId)).thenReturn(createUserWithEmail(email)); notifyService.sendMessage(userId, "hello"); verify(mc).sendEmail(eq(email), eq("hello")); }
?
?
语义不言自明
测试前,从uc获得email
测试后,必须调用mc.sendEmail,所以验证之
?
通过一个简单的例子,可以看到:mockito在使我们代码看直接,语义更明确
?
?