ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Junit测试案例几种特殊情况 | 进阶篇

2021-02-23 21:29:42  阅读:145  来源: 互联网

标签:案例 DemoUtil class Junit 进阶篇 org Test import junit


本文针对Junit测试案例编写中碰到的几种复杂情况给出解决方法,属于进阶使用,不介绍基础的Junit运用

Junit测试案例


Junit案例

说明:Junit是持续集成用得最广泛的工具之一,基础部分不展开描述,本文针对junit几种特殊情况,进阶使用PowerMock工具解决测试案例难题


一、PowerMock工具

说明:正常的if分支和模拟数据,通过Mock工具就可以完成了,但Mock没办法模拟很多复杂的情景,比如私有方法、静态方法的引用,被测试类的new对象等等。
此时就需要引入功能更强大的PowerMock工具,实现各种复杂场景下的分支覆盖。

下面是几种常见的特殊案例:

1.静态方法

案例:

package junit.ding.test;

import junit.ding.util.DemoUtil;

public class Demo {
	public String fun1(){
		//这里引用了DemoUtil的静态方法
		//测试fun1时需要屏蔽这个方法
		String str = DemoUtil.getStr();
		return str;
	}
}

junit测试案例:

静态方法无法直接通过PowerMockito.when来实现屏蔽,但可以先调用PowerMockito.mockStatic方法,在底层对类属性进行修改。

案例如下,调用mockStatic之前先实现注解
@RunWith(PowerMockRunner.class)
@PrepareForTest({DemoUtil.class})

package junit.ding.test;

import junit.ding.test.Demo;
import junit.ding.util.DemoUtil;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({
	DemoUtil.class
})
@PowerMockIgnore("javax.management.*")
public class DemoTest {
	@Test
	public void testFun1() {
		Demo demo = new Demo();
		PowerMockito.mockStatic(DemoUtil.class);
		PowerMockito.when(DemoUtil.getStr()).thenReturn("Test");
		Assert.assertEquals("Test", demo.fun1());
	}
}

2.private方法

private禁止被外部类访问,所以测试案例最典型的难题就是Test类无法屏蔽被测试类的内部private或父类private方法。

案例:需要测试fun,但是fun内部调用了add方法,add方法为private权限,无法被外部Test类调用,即无法显式地mock出来。

package junit.ding.test;

public class Demo {
	public int fun(int i){
		i = 1;
		return add(i);
	}
	
	private int add(int i){
		return ++i;
	}
}

junit测试案例:通过PrepareForTest在底层修改方法的可见性,这里用到了Matchers匹配mock方法的入参。
通过PowerMock的thenCallRealMethod和thenReturn可以实现屏蔽该方法或者实际执行。

提示:也可以用反射的机制实现private调用,后面会介绍。

package junit.ding.test;

import junit.ding.test.Demo;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({
	Demo.class
})
@PowerMockIgnore("javax.management.*")
public class DemoTest {

	@Test
	public void testPrivate() throws Exception{
		Demo demo = PowerMockito.mock(Demo.class);
		PowerMockito.when(demo,"add",Matchers.any(Integer.class)).thenReturn(3);
		PowerMockito.when(demo,"fun",Matchers.any(Integer.class)).thenCallRealMethod();
		Assert.assertEquals(3, demo.fun(1));
	}

}

3.方法内部new对象

如果在被测试类的内部里new对象,且new出来的对象内部调用了复杂的逻辑,为了屏蔽这些方法,比如在junit测试类中同样new对象。 但因为Test类中新建的对象属于局部对象,无法在源类的作用域中生效,所以Junit跑进去时会报NullPointer异常。

案例:PowerMock提供whenNew方法解决此类问题。

package junit.ding.test;

import junit.ding.util.DemoUtil;

public class Demo {
	public String fun(){
		//如下,引用DemoUtil对象并调用其方法
		DemoUtil demoUtil = new DemoUtil();
		demoUtil.loadFile("Test");
		return demoUtil.reserveLog("log");
	}
}

junit测试案例:如下,使用PowerMockito.whenNew可以解决局部对象的作用域问题。

package junit.ding.test;

import junit.ding.test.Demo;
import junit.ding.util.DemoUtil;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({
	DemoUtil.class
})
@PowerMockIgnore("javax.management.*")
public class DemoTest {
	@Test
	public void testMockNew() throws Exception{
		Demo demo = new Demo();
		DemoUtil demoUtil = PowerMockito.mock(DemoUtil.class);
		PowerMockito.whenNew(DemoUtil.class).withNoArguments().thenReturn(demoUtil);
		Assert.assertEquals("Test", demo.fun());
	}
}

4.void方法

被测试类中经常会出现void方法,内部逻辑复杂,对于我们要测试的类来说,不需要执行那么复杂的逻辑,为了屏蔽这类方法,可以调用PowerMock的donothing方法。

案例:

package junit.ding.test;

public class Demo {
	public void fun(){
		System.out.println("do nothing");
	}
	
	public void funVoid(){
		System.out.println("sleep");
	}
}

Junit案例:

package junit.ding.test;

import junit.ding.test.Demo;
import junit.ding.util.DemoUtil;
import org.junit.Test;
import org.powermock.api.mockito.PowerMockito;
public class DemoTest {
	@Test
	public void testMockNew() throws Exception{
		Demo demo = new Demo();
		PowerMockito.doNothing().when(demo).funVoid();
		//静态方法也支持
		PowerMockito.doNothing().when(DemoUtil.class,"loadFile",new String());
		demo.fun();
	}
}

二、Override重写类实现mock

1.方法重写

除了上述的PowerMock工具外,也可以在Test类中重写类的方法,达到屏蔽的效果,前提是该方法可重写,即非final也非private属性。

案例:

package junit.ding.test;
import junit.ding.util.DemoUtil;
public class Demo {
	public String fun(){
		return funProtected();
	}
	
	protected String funProtected(){
		return DemoUtil.loadFile("Test");
	}
}

Junit案例:如下图,无需调用第三方DemoUtil类,直接重写Demo的funProtected方法,屏蔽DemoUtil。

package junit.ding.test;
import junit.ding.test.Demo;
import org.junit.Assert;
import org.junit.Test;
public class DemoTest {
	@Test
	public void testMockNew() throws Exception{
		Demo demo = new Demo(){
			@Override
			protected String funProtected(){
				return "Test";
			}
		};
		Assert.assertEquals("Test", demo.fun());
	}
}

2.利用反射构造对象

提示:此处比较难,需要先学会java反射的概念。
由上,如果方法属于不可重写方法,可以通过反射机制,修改方法的访问权限。

案例如下:Demo的对象DemoUtil可见性是private,

package junit.ding.test;
import junit.ding.util.DemoUtil;
public class Demo {
	//这里的DemoUtil对象是private
	private DemoUtil demoUtil;
	public String fun(){
		return demoUtil.loadFile("Test");
	}
}

Junit案例:代码如下,根据实例,将被测试类的层层父类属性都设为可见,并同步修改局部对象的作用域。

package junit.ding.test;
import java.lang.reflect.Field;
import junit.ding.test.Demo;
import junit.ding.util.DemoUtil;

import org.junit.Assert;
import org.junit.Test;
public class DemoTest {
	@Test
	public void testMockNew() throws Exception{
		Demo demo = new Demo();
		setPrivateValue(demo,"demoUtil",new DemoUtil(){
			@Override
			public String loadFile(String str){
				return "Test";
			}
		});
		Assert.assertEquals("Test", demo.fun());
	}
	
	//设置被测试类的对象为可访问
	public void setPrivateValue(Object obj, String fieldName, Object expectValue){
		//obj为被测试类实例,通过实例获取被测试类的Class
		Class<?> clazz = obj.getClass();
		try{
			//循环找到实例的父类,Object为所有对象的父类
			for(;clazz!=Object.class;clazz = clazz.getSuperclass()){
				try {
					//更改实例类的对象属性
					Field field = clazz.getDeclaredField(fieldName);
					field.setAccessible(true);
					field.set(obj, expectValue);
					break;
				} catch (Exception e) {
					// TODO: handle exception
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

也可以使用getDeclaredMethod:

Class<DemoUtil> clazz1 = DemoUtil.class;
Method method = clazz1.getDeclaredMethod("loadFile", String.class);
method.setAccessible(true);
method.invoke(demo, "Test");

后记

以上就是比较复杂的测试案例写法,可能还有其他的案例,但结合上面几种解决办法,可以解决大部分的测试案例问题。
1、比如当需要测试的方法也是private时,除了用反射,也可以通过PowerMock.doMethod的方法进行调用,本文不继续展开了,读者碰到其他情况也可以查看PowerMock官方文档。
2、反射除了使用getDeclaredField和clazz.getDeclaredMethod()之外,也可以使用getDeclaredConstructor.newInstance()等,这部分可以扩展反射的知识,不赘述。

标签:案例,DemoUtil,class,Junit,进阶篇,org,Test,import,junit
来源: https://blog.csdn.net/yesLeslie/article/details/113928425

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有