单元测试工具-PowerMock

1 篇文章 0 订阅
单元测试工具-PowerMock

	Mock:为某个类创建一个虚假的对象,在测试时用这个虚假的对象替换掉真实的对象。

	PowerMock实现原理:
		1)PowerMock会创建一个org.powermock.core.classloader.MockClassLoader类加载器,然后该类加载器会去加载测试用例使用到的类(系统类除外)。
		2)PowerMock会去修改写在注解@PrepareForTest里的class文件(注:当前测试类会自动加入该注解中),以实现mock的功能。
		3)如果需要mock系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件。

	UT指标:
		行覆盖、函数覆盖、分支覆盖
		增量行覆盖、增量函数覆盖、增量分支覆盖
		注意:AClassTest和AClass的包路径必须一致,否则单测覆盖率统计不到。 

	实现:
		第一步:
			maven依赖:
		        <dependency>
		            <groupId>junit</groupId>
		            <artifactId>junit</artifactId>
		            <version>4.8</version>
		            <scope>test</scope>
		        </dependency>


		        <dependency>
		            <groupId>org.powermock</groupId>
		            <artifactId>powermock-module-junit4</artifactId>
		            <version>1.7.0</version>
		            <scope>test</scope>
		        </dependency>
		        <dependency>
		            <groupId>org.powermock</groupId>
		            <artifactId>powermock-api-mockito</artifactId>
		            <version>1.7.0</version>
		            <scope>test</scope>
		        </dependency>

		第二步:
			添加注解:
				@RunWith(PowerMockRunner.class)
				@PrepareForTest({ClassA.class})
				public class UserServiceImplTest {

					@InjectMocks
					private ClassA clazzA; 

					@Mock
					private ClassB clazzB;  

					@Mock
    				private ClassC clazzC;

    				// 实例化@InjectMocks注解修饰的属性!
				    @Before
				    public void setUp() {
				        MockitoAnnotations.initMocks(this);
				    }


					@Test
					public void testMethodA() {

						// Mock一般方法:
						List myList = new ArrayList();
						PowerMockito.when(clazzA.methodA(Mockito.anyInt(), Mockito.anyString(), Mockito.anyObject()).thenReturn(myList);
						List result = clazzA.methodA(17, "test", new ArrayList());
						Assert.assertEquals(myList, result);


					}
				}

				说明:
					@RunWith(PowerMockRunner.class)注解:表示使用PowerMockerRunner来运行测试用例,否则无法使用powermock。

					@PrepareForTest({xx.class, xxx.class})注解:声明需要测试的类(即:要测试的目标方法所在的类 + 目标方法中使用到的且需要mock的其它类),以逗号分隔。
					注意:
						1>普通Mock(mock一般方法)不需要加@RunWith(PowerMockRunner.class)和@PrepareForTest({xx.class, xxx.class})注解!
						2>非普通mock(mock静态方法、私有方法、构造方法、final方法、抽象方法)必须加@RunWith(PowerMockRunner.class)和@PrepareForTest({xx.class, xxx.class})注解!

					@InjectMocks注解:Mockito会将@Mock或@Spy修饰的属性自动注入到@InjectMocks修饰的属性中。

					@Mock注解:为某个类创建一个虚假的对象,在测试时用这个虚假的对象替换掉真实的对象。

					@Spy注解:
						1>当一个对象中的部分方法需要mock,部分方法需要真实调用的时候,我们可以使用该注解来解决这个问题。
						2>被@spy修饰的对象:
						
							若方法已经打桩(打桩方式有特定要求,不是所有形式的桩都可以),则走打桩逻辑;否则走正常调用逻辑。
							
							特别注意:
								Mockito.doReturn(obj).when(xxxService).methodxx(anyObject()));  // 这种打桩方式有效,打桩后会走mock逻辑。
								Mockito.when(xxxService.methodxx(anyObject())).thenReturn(obj); // 这种打桩方式无效,打桩后仍然会走正常调用逻辑!

						3>被@spy修饰的对象:属性不会自动注入,可以通过反射来手动赋值。eg:MyTransactionalTest

					要想使以上注解生效,则必须在执行测试方法前 先实例化@InjectMocks注解修饰的属性:MockitoAnnotations.initMocks(this);


				Mock方法: 

					注意:如果方法的参数不是mock出来的,则调用方法的实例就不会是mock出来的了!!!

					Mock一般方法:
						PowerMockito.when(clazzA.methodA(Mockito.anyObject())).thenReturn("ok");
						PowerMockito.doThrow(new XxxException("")).when(clazzA).methodA(Mockito.any(XXX.class))
						注意:doThrow中抛出异常的范围不能超过methodA方法抛出的异常的范围。若methodA没有显示声明抛出异常,则我们可以thenThrow(new RuntimeException(""))。

					Mock静态方法:
						PowerMockito.mockStatic(MyUtils.class);
						PowerMockito.when(MyUtils.methodA()).thenReturn("ok");
						注:若MyUtils的方法为static void ,则默认会mock掉。

					Mock私有方法:
						PowerMockito.when(clazzA, "methodB").thenReturn("ok");
						PowerMockito.when(clazzA, "methodB", param1, param2).thenReturn("ok");
						PowerMockito.when(clazzA, "methodB").thenCallRealMethod();

						举例:PowerMockito.when(clazzA, "methodB", Mockito.anyObject(), Mockito.anyObject()).thenReturn("ok");

						说明:也可以考虑使用反射来修改类的私有属性、调用类的私有方法。
						举例:
							ReflectionTestUtils.setField(targetObject, "token", "123");
							ReflectionTestUtils.invokeMethod(targetObject, "methodName", param1, param2);


					Mock构造方法:
						说明:当我们需要mock方法内new出来的变量时,我们可以通过mock构造方法来实现。
						// mock file对象
						PowerMockito.whenNew(File.class).withArguments(Mockito.anyString()).thenReturn(new File("aaa")); 

						// mock file对象并且打桩
				        File mockFile = Mockito.mock(File.class);
				        PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(mockFile);
				        PowerMockito.when(mockFile.canExecute()).thenReturn(false);
				        // PowerMockito.when(mockFile.canExecute()).thenThrow(new RuntimeException());


					Mock抽象方法:
						说明:我们一般选择去mock抽象类的实现类,而不是直接mock抽象类。


				断言的场景:
					断言方法的返回值与期望的返回值相同:		Assert.assertEquals(result, expectResult);
					断言方法被调用n次
						一般用来判断void方法:	Mockito.verify(aaaService, Mockito.times(n)).method1(Mockito.anyString(), Mockito.<Function>anyObject(), Mockito.any(Xxx.class)); 

						使用PowerMockito检查某个静态方法调用的次数:
							步骤:先调用对应的静态方法,再启用静态检查,最后再调用对应的静态方法。这样的用法比较奇怪,记住即可。
							举例:验证Runtime.getRuntime()是否被调用了2次。

								PowerMockito.mockStatic(Runtime.class);

								Runtime.getRuntime();
								Runtime.getRuntime();
								// do other things

								PowerMockito.verifyStatic(Mockito.times(2));
								Runtime.getRuntime();

					
					
	常见的错误:

        
		打桩时,若mock出来的方法入参(Mockito.anyLong()等方法)无法打桩成功时,将所有的入参替换为调用时的传入真实值即可解决问题。eg:

			Mockito.when(xxxService.methodA(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(new ArrayList());
			替换为:
			Mockito.when(xxxService.methodA(111L, false)).thenReturn(new ArrayList());


		org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'.
		解决:when()方法中的参数要求是一个mock对象的方法调用。报该错误的原因是调用方法的对象不是mock出来的对象。如果是调用静态方法,则使用PowerMockito.mockStatic(类名.class);


		org.powermock.api.mockito.ClassNotPreparedException: The class XXX not prepared for test.
		解决:将XXX.class添加到@PrepareForTest中。


		
	与其它单测模式混用:

	    @RunWith(SpringRunner.class)
	    @SpringBootTest(classes = Application.class)
	    @Transactional
	    @Rollback
	    public class MyTransactionalTest {

	        @Resource
	        private AaaService aaaService;

	        @Mock 
	        private BbbService bbbService;

	        @spy
	        private CccService cccService;

			@Resource
	        private DddService dddService;

	        
	        @Before
	        public void initMock() {
	            MockitoAnnotations.initMocks(this);

	            // 使用反射,将mock的对象直接注入到指定类中,之后我们就可以对这个mock的对象进行打桩操作了。说明:如果没有对mock的对象进行打桩,则该对象方法的调用走默认的mock逻辑。
	            ReflectionTestUtils.setField(aaaService, "bbbService", bbbService);


	            // 使用反射,将spy的对象直接注入到指定类中,之后我们就可以对这个spy的对象进行打桩操作了。说明:如果没有对spy的对象进行打桩,则该对象方法的调用走正常调用逻辑。
	            ReflectionTestUtils.setField(aaaService, "cccService", cccService);


	            // 使用反射,给@spy修饰的对象的属性手动赋值。
	            ReflectionTestUtils.setField(cccService, "dddService", dddService);
	        }
	    }


	    
    

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值