单元测试系统化讲解之PowerMock

1,737 阅读6分钟

单元测试系统化讲解之PowerMock

阅读本篇博客前建议有Mockito基础,参见上一篇文章:单元测试系统化讲解之Mockito

  • 什么是PowerMock
  • Mock局部变量
  • Mock静态方法
  • Mock final 修饰的方法
  • Mock私有方法
  • Verify的使用
  • Mock不同的构造函数
  • Parameters Matcher接口的使用
  • Answer接口的使用
  • Spy的使用

一、什么是PowerMock

1.1 前述

  • 本次讲解的PowerMock是单元测试的进阶技术框架;所以学习PowerMock中,博主假设你们已经满足如下条件:

    • 知道什么是单元测试
    • 明白Junit/Mockito相关的一些使用或知识
    • 想要了解单元测试相关的一些技术
  • PowerMock是什么?

    • PowerMock是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法、构造方法、final类和方法、私有方法、去除静态初始化器等等。
    • 通过使用自定义的类加载器,简化采用的IDE或持续集成服务器不需要做任何改变。
    • 熟悉PowerMock支持的mock框架的开发人员会发现PowerMock很容易使用,因为对于静态方法和构造器来说,整个的期望API是一样的。
    • PowerMock旨在用少量的方法和注解扩展现有的API来实现额外的功能。目前PowerMock支持EasyMock和Mockito。
    • PowerMock是基于其他Mock框架,做的增强。所以不能单独使用它。
  • PowerMock解决了哪些痛点?

    • 现如今比较流行的Mock工具如jMock,EasyMock,Mockito等都有一个共同的缺点:不能mock静态、final、私有方法等。而PowerMock能够完美的弥补以上三个Mock工具的不足。
    • 能够mock方法内new 出来的对象
  • 注意:为什么总说PowerMock尽量少用?

    • PowerMock是一个非常强大且有用的工具。 但是,仅在绝对必要时才应使用它,因为它会对测试执行时间产生巨大影响。
    • 缺乏API知识,过于严格的可见性和直接的静态方法调用。
    • 如果您发现测试代码库中充满了PowerMock的用法,建议您尝试上述方法以摆脱它们。
    • 如果单元测试里面出现了太多的不必要的PowerMockito使用,那么这个单元测试的质量是有待考量的,特别是一些私有方法尽量不用使用PowerMockito,善PowerMockito,尽量提高单元测试的真实性.

1.2 PowerMock快速入门

  • PowerMock有两个重要的注解,分别是:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest( { YourClassWithEgStaticMethod.class })
    
  • 使用了@PrepareForTest注解,能够使用PowerMock的强大功能(mock静态、final、私有方法等);需要注意的是,它不能单独生效,我们必须要同时加上@RunWith(PowerMockRunner.class)

  • 使用PowerMock的依赖项:

    <dependency>
    	<groupId>org.powermock</groupId>
    	<artifactId>powermock-module-junit4</artifactId>
    	<version>1.6.5</version>
    	<scope>test</scope>
    </dependency>
    <dependency>
    	<groupId>org.powermock</groupId>
    	<artifactId>powermock-api-mockito</artifactId>
    	<version>1.6.5</version>
    	<scope>test</scope>
    </dependency>
    
  • 开始测试:

    1. 编写被测试的类:

      public class UserService{
      
      	private UserDao userDao;
      	
      	public UserService(UserDao userDao){
      		this.userDao = userDao;
      	}
      
      	public int querUserCount(){
      		return userDao.getCount();
      	}
      
      	public void saveUser(User user){
      		userDao.insertUser(user);
      	}
      }	
      
    2. 测试类:

      public class UserServiceTest{
      
      	private UserService userService;
      
      	@Before
      	public void setUp(){
      		userService = new UserService(new UserDao());
      	}
      
      	@Mock
      	private UserDao userDao;
      
      	@Test
      	public void testQueryUserCountWithPowerMock(){
      		UserDao uDao = PowerMockito.mock(UserDao.class);
      		PowerMockito.when(uDao.getCount()).thenReturn(10);
      		UserService service = new UserService(uDao);
      		int result = service.queryUserCount();
      		assertEquals(10,result);
      	} 
      	
      	
      	@Test
      	public void testQueryUserCountWithMockito(){
      		MockitoAnnotations.initMocks(this);
      		UserService service = new UserService(userDao);
      		Mockito.when(userDao.getCount()).thenReturn(10);
      	
      		int result = service.queryUserCount();
      		assertEquals(10,result);
      	}
      
      	
      	@Test
      	public void testSaveUserWIthJunit(){
      		try{
      			userService.saveUser(new User());
      			fail("should not process to here");
      		}catch (Exception e){
      			assertTrue(e instanceof UnsupportedOperationException);
      		}
      	}	
      }
      

代码中展示了Junit、mockito、powermock的三种测试方式。

二、PowerMock实战演示

2.1 在方法内使用局部变量

  • 被测试类:
public class UserService{
	
	public int queryUserCount(){
		UserDao userDao = new UserDao();
		return userDao.getCount();
	}
	
	public void saveUser(User user){
		UserDao userDao = new userDao();
		userDao.insertUser(user);
	}
}
  • 测试类
@RunWith(PowerMockRunner.class)
@PrepareForTest(UserService.class)
public class UserServiceTest{

	@Test
	public void tesetQueryUserCount(){
		try{
			UserService userService = new UserService();
			UserDao userDao = mock(UserDao.class);
			whenNew(UserDao.class).withNoArguments().thenReturn(userDao);
			doReturn(10).when(userDao).getCount();
			int result = userService.queryUserCount();
			assertEquals(10,result);
		}catch(Throwable e){
			fail();
		}
	}

	@Test
	public void testSaveUser(){
		try{
			User user = new User();
			UserService userService = new UserService();
			UserDao userDao = mock(UserDao.class);
			whenNew(UserDao.class).withAnyArguments().thenReturn(userDao);
		
			userService.saveUser(user);
			Mockito.verify(userDao,Mockito.times(1)).insertUser(user);
		}catch(Throwable e){
			fail();
		}
	}
}

2.2 Mock静态方法

  • 静态Dao层
public class UserDao{
	
	public static int getCount(){
		throw new UnsupportedOperationException();
	}

	public static void insertUser(User user){
		throw new UnsupportedOperationException();
	}
}
  • 被测试的Service层
public class UserService{

	public int queryUserCount(){
		return UserDao.getCount();
	}

	public void saveUser(User user){
		UserDao userDao = new UserDao();
		UserDao.insertUser(user);
	}
}
  • 测试类
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class,UserDao.class})
public class UserServiceTest{
	
	@Test
	public void testQueryUserCount() throws Exception{
		PowerMockito.mockStatic(UserDao.class);
		PowerMockito.when(UserDao.getCount()).thenReturn(10);
		UserService userService = new UserService();
		int result = userService.queryUserCount();
		assertEquals(10,result);
	}
	
	@Test
	public void testSaveUser() throws Exception{
		mockStatic(UserDao.class);
		User user = new User();
		doNoting().when(UserDao.class);
		UserService userService = new UserService();
		userService.saveUser(user);
		
		PowerMockito.verifyStatic();

	}
}

2.3 final 修饰的类

  • final 类
final public class UserDao{
	public int getCount(){
		throw new UnsupportedOperationException();
	}

	public void insertUser(User user){
		throw new UnsupportedOperationException();
	}
}
  • Service类
public class UserService{

	private UserDao userDao;

	public UserService(UserDao userDao){
		this.userDao = userDao;
	}
	
	public int queryUserCount(){
		return userDao.getCount();
	}

	public void saveUser(User suer){
		userDao.insertUser(user);
	}
}
  • 测试类
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class,UserDao.class})
public class UserServiceTest{

	@Test
	public void testQueryUserCountWithPowerMock() throws Exception{
		UserDao uDao = PowerMockito.mock(UserDao.class);
		System.out.println(uDao.getClass());
		PowerMockito.when(uDao.getCount()).thenReturn(10);
		UserService userService = new UserService(uDao);
		int result = userService.queryUserCount();
		assertEquals(10,result);
	}
}

2.4 verify的使用

  • UserDao类
public class UserDao{

	public int getCount(User user){
		throw new UnsupportedOperationException();
	}

	public void updateUser(User user){
		throw new UnsupportedOperationException();
	}

	public void insertUser(User user){
		throw new UnsupportedOperationException();
	}

}
  • UserService
public class UserService{

	public void saveOrUpdate(User user){
		UserDao userDao = new UserDao();
		if(userDao.getCount(user) > 0){
			userDao.updateUser(user);
		}else{
			userDao.insertUser(user);
		}
	}

}
  • UserServiceTest
@RunWith(PowerMockRunner.class)
@PrepareForTest(UserService.class)
public class UserSErviceTest{

	@Test
	public void testSaveOrUpdateWillUseNewJoiner() throw Exception {
		User user = PowerMockito.mock(User.class);
		UserDao userDao = PowerMockito.mock(UserDao.class);
		PowerMockito.whenNew(UserDao.class).withAnyArguments().thenReturn(user);
		PowerMockito.when(userDao.getCount(user)).thenReturn(0);

		UserService userService = new UserService();
		userService.saveOrUpdate(user);
	}

	@Test
	public void testSaveOrUpdateWillUseUpdateOriginal() throw Exception{
		User user = PowerMockito.mock(User.class);
		UserDao userDao = PowerMockito.mock(userDao.class);
		PowerMockito.whenNew(UserDao.class).withAnyArguments().thenRetuurn(userDao);
		PowerMockito.when(userDao.getCount(user)).thenReturn(1);
		UserService userService = new UserService();
		userService.saveOrUpdate(user);
		
		Mockito.verify(userDao).insertUser(user);
		Mockito.verify(userDao,Mockito.never()).updateUser(user);		// never表示一次都不会执行
	}
}

verify有很多方法,同学们可以看看源码或者官方文档来进行使用和练习;

2.5 Mock 构造函数

  • UserDao
public class UserDao{

	private String username;

	private String password;

	public UserDao(String username,String password){
		this.username = username;
		this.password = password;
	}

	public void insert(){
		throw new UnsupportedOperationException();
	}

}
  • UserService
public class UserService{

	public void save(String username, String password){
		UserDao userDao = new UserDao(username,password);
		userDao.insert();
	}

}
  • UserServiceTest
@RunWith(PowerMockRunner.class)
@PrepareForTest(UserService.class)
public class UserServiceTest{
	
	@Test
	public void testSave() throws Exception(){
		UserDao userDao = PowerMockito.mock(UserDao.class);
		String username = "anyu";
		String passsword = "lyle";
		PowerMocktio.whenNew(UserDao.class).withArguments(username,passsword).thenReturn(userDao);
		
		PowerMockito.doNoting().when(userDao).insert();
		UserService userService = new UserService();
		
		Mockito.verify(userDao).insert();
	}

}


2.6 Arguments Matcher的使用

  • UserDao
public class UserDao{
	public String queryByName(String username){
		throw new UnsupportedOperationException();
	}
}
  • UserService
public class UserService{

	public String find(String name){
		UserDao userDao = new UserDao();
		return userDao.queryByName(name);
	}

}
  • UserServiceTest
@RunWith(PowerMockRunner.class)
@PrepareForTest(UserService.class)
public class UserServiceTest{

	@Test
	public void testFind() throws Exception{
		UserDao userDao = PowerMockito.mock(UserDao.class);
		PowerMockito.whenNew(UserDao.class).withAnyArguments().thenReturn(userDao);

		PowerMockito.when(userDao.queryByName("lyle")).thenReturn("anyu");
		UserService service= UserService();
		String result = service.find("lyle");
		assertEquals("anyu",result);
	
		PowerMockito.when(userDao.queryByName("Jacky")).thenReturn("anyu");
		UserService service= UserService();
		String result = service.find("lyle");
		assertEquals("anyu",result);

		PowerMockito.when(userDao.queryByName("Tommy")).thenReturn("anyu");
		UserService service= UserService();
		String result = service.find("lyle");
		assertEquals("anyu",result);	

	}

	// 使用match
	@Test
	public void testFindWithMatcher() throw Exception{

		UserDao userDao = PowerMockito.mock(UserDao.class);
		PowerMockito.whenNew(UserDao.class).withAnyArguments().thenReturn(userDao);	//CSDN:暗余
		PowerMockito.when(userDao.queryByName(Matchers.argThat(new MyArgumentMatcher()))).thenReturn("anyu");
		UserService service = new UserService();
		
		assertEquals("lyle",service.find("lyle"));
		assertEquals("lyle",service.find("Jacky"));
		assertEquals("lyle",service.find("Van"));
		assertEquals("lyle",service.find("Tony"));
	}

	static class MyargumentMatcher extends ArgumentMatcher<String>{
		@Override
		public boolean matches(Object o){
			String arg = (String) o;
			switch(arg){
				case "lyle":
				case "Jacky":
				case "Van":
				case "Tony":
					return true;
				default:
					return false;
			}
		}
	}

}

2.7 Answer 接口的使用

  • UserDao
public class UserDao{
	public String queryByName(String username){
		throw new UnsupportedOperationException();
	}
}
  • UserService
public class UserService{

	public String find(String name){
		UserDao userDao = new UserDao();
		return userDao.queryByName(name);
	}

}
  • UserServiceTest
@RunWith(PowerMockRunner.class)
@PrepareForTest(UserService.class)
public class UserServiceTest{
	
	@Test
	public void testFindWithAnswer() throw Exception{

		UserDao userDao = PowerMockito.mock(UserDao.class);
		PowerMockito.whenNew(UserDao.class).withAnyArguments().thenReturn(userDao);
		
		PowerMockito.when(userDao.queryByName(Mockito.anyString())).then(invocation -> {
			String arg = (String) invocation.getArguments()[0];
			switch(arg){
				case "lyle":
					return "I am csdn 暗余";
				case "Alex":
					return "I am alex";
				default:
					throw new RuntimeException("Not support "+ arg);
			}
		});
		UserService service = new UserService();
		assertEquals("I am casn 暗余",service.find("lyle"));
		assertEquals("I am alex", service.find("Alex"));
	}	
}

2.8 Spy的使用

  • UserService
public class UserService{
	public void foo(){
		log(); 
	}
	
	private void log(){
		System.out.println("I am console log.");
	}

	public boolean exist(String username){
		return checkExist(username);
	}
	
	private boolean checkExist(String username){
		throw new UnsupportedOperationException();
	}
}

  • 使用spy会调用真实的方法: 在这里插入图片描述

  • 如果使用了when函数指定了对应方法的请求参数,当传入对应的请求参数时会调用mock的方法不会打印任何内容; 在这里插入图片描述

  • 而不满足条件,则会继续执行真实的方法: 在这里插入图片描述

  • 测试私有方法:

public class UserServiceTest{
	
	@Test
	public void testCheck() throws Exception{
		UserService userService = PowerMockito.apy(new UserService());
		PowerMockito.doReturn(true).when(userService,"checkExist","lyle");
		assertTrue(userService.exist("lyle"));
		// 这个参数没有被定义,所以会走本身的方法,即触发异常
		userService.exist("other");
	}

}

写文不易,觉得不错点个赞再走吧~ ^^