Junit4.12+powermock+mock单元测试静态⽅法、普通⽅法、私
有⽅法
⾸先,我先引⽤⼤神的⼀些语⾔解释⼀下mock对单元测试的作⽤。
参考博客:
1、为什么要⽤mock
我的⼀本书的解释:
(1)创建所需的DB数据可能需要很长时间,如:调⽤别的接⼝,模拟很多数据
(2)调⽤第三⽅API接⼝,测试很慢,
(3)编写满⾜所有外部依赖的测试可能很复杂,复杂到不值得编写,Mock模拟内部或外部依赖可以帮助我们解决这些问题
另⼀本TDD书的解释:
(1)对象的结果不确定,如每获取当前时间,得到的结果都不⼀样,⽆法符合我们的预期;
(2)实现这个接⼝的对象不存在;
(3)对象速度缓慢
对于TDD还有⼀个更重要原因:通过模拟可以隔离当前⽅法使⽤的的所有依赖,让我们更加专注于单个单元,忽略其调⽤的代码的内部⼯作原理
⼀本博客的⼲货:
(1)Mock可以⽤来解除测试对象对外部服务的依赖(⽐如数据库,第三⽅接⼝等),使得测试⽤例可以独⽴运⾏。不管是传统的单体应⽤,还是现在流⾏的微服务,这点都特别重要,因为任何外部依赖的存在都会极⼤的限制测试⽤例的可迁移性和稳定性。
(2)Mock的第⼆个好处是替换外部服务调⽤,提升测试⽤例的运⾏速度。任何外部服务调⽤⾄少是跨进程级别的消耗,甚⾄是跨系统、跨⽹络的消耗,⽽Mock可以把消耗降低到进程内。⽐如原来⼀次秒级的⽹络请求,通过Mock可以降⾄毫秒级,整整3个数量级的差别。
(3)Mock的第三个好处是提升测试效率。这⾥说的测试效率有两层含义。第⼀层含义是单位时间运⾏的测试⽤例数,这是运⾏速度提升带来的直接好处。⽽第⼆层含义是⼀个测试⼈员单位时间创建的测试⽤例数。
以单体应⽤为例,随着业务复杂度的上升,为了运⾏⼀个测试⽤例可能需要准备很多测试数据,与此同时还要尽量保证多个测试⽤例之间的测试数据互不⼲扰。为了做到这⼀点,测试⼈员往往需要花费⼤量的时间来维护⼀套可运⾏的测试数据。有了Mock之后,由于去除了测试⽤例之间共享的数据库依赖,测试⼈员就可以针对每⼀个或者每⼀组测试⽤例设计⼀套独⽴的测试数据,从⽽很容易的做到不同测试⽤例之间的数据隔离性。⽽对于微服务,由于⼀个微服务可能级联依赖很多其他的微服务,运⾏⼀个测试⽤例甚⾄需要跨系统准备⼀套测试数据,如果没有Mock,基本上可以说是不可能的。因此,不管是单体应⽤还是微服务,有了Mock之后,QE就可以省去⼤量的准备测试数据的时间,专注于测试⽤例本⾝,⾃然也就提升了单⼈的测试效率。
现如今⽐较流⾏的Mock⼯具如jMock 、EasyMock 、Mockito等都有⼀个共同的缺点:不能mock静态、final、私有⽅法等。⽽PowerMock能够完美的弥补以上三个Mock⼯具的不⾜
2、实战:
在实战过程中,mock的版本是我花了⼀天时间才解决的坑,我使⽤的是阿⾥云的maven,有些博客的powermock的依赖依赖不进来,经过多次尝试,才解决这个问题。下⾯是代码。
参考博客:
<!--mock测试-->
<properties>
<java.version>1.8</java.version>
<powermock-version>2.0.5</powermock-version>
<mockito-version>2.23.4</mockito-version>
</properties>
<dependency>
<groupId&kito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>${powermock-version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule</artifactId>
<version>${powermock-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
测试类包含了普通的⽅法调⽤,静态⽅法、私有⽅法等。
stmock;
import org.junit.Test;
import org.junit.runner.RunWith;
kito.InjectMocks;
kito.Mock;
kito.Mockito;
import org.kito.PowerMockito;
import lassloader.annotations.PrepareForTest;
import dules.junit4.PowerMockRunner;
import flect.Whitebox;
import flect.InvocationTargetException;
import flect.Method;
import static org.junit.Assert.assertEquals;
import kito.ArgumentMatchers.any;
import static org.kito.PowerMockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest(UserController.class)
public class LastMock {
@Mock
private UserServiceImpl serviceImpl;
@InjectMocks
private UserController controller;
/**
* mock service的保存⽅法
*/
@Test
public void mockSave() {
User user1 = new User();
User user2 = new User();
user1.setId("1");
user2.setId("2");
Mockito.when(serviceImpl.save(user1)).thenReturn(user2); //当调⽤service的save()时,mock让他返回user2 User saveUser = controller.saveUser(user1); //调⽤
Mockito.verify(serviceImpl,Mockito.times(1)).save(user1);//verify验证mock次数
assertEquals(user2, saveUser);//断⾔是否mock返回的是user2
}
/**
* mock spy public⽅法
* @throws Exception xx
*/
@Test
public void spy_public_method() throws Exception {
UserController spy = PowerMockito.spy(controller); //监视controller的publicCheck⽅法,让他返回true
Mockito.when(spy.publicCheck()).thenReturn(true);
String name = PrivateName("ljw");//执⾏该⽅法
assertEquals("public 被mock 了", name);//验证
}
/**
* mock私有⽅法
* @throws Exception xx
*/
@Test
public void spy_private_method() throws Exception {
UserController spy = PowerMockito.spy(controller);
PowerMockito.when(spy, "check", any()).thenReturn(true);//私有⽅法mockito不⾏了,需要⽤⽆所不能的PowerMock监视spy
String name = PrivateName("ljw");
assertEquals("private 被mock 了", name);
}
/**
* mock 静态⽅法
*/
@Test
public void mockStaticMethod() {
StaticName(any())).thenReturn("hi");
String staticName = StaticName("ljw");//执⾏
assertEquals("hi", staticName);//验证
}
@Test
public void mockStaticMethod_2() {
StaticName(any())).thenReturn("hi");
String staticName = urnName();//通过returnName()调⽤,看能否被mock
assertEquals("hi", staticName);
}
/**
*静态⽅法传⼊⼀个HttpServerletRequest参数 + 普通⽅法
*sA(T.class)检查参数T的实例instance,表⽰它为⾮null。
*same(obj)检查参数是否与obj相同,从⽽arg == obj为true。
*eq(obj)根据其equals⽅法检查参数是否等于obj。如果您在不使⽤匹配器的情况下传递实数值,这也是⾏为。
*/
@Test
public void mockStaticMethod_3() {
PowerMockito.when(UserController.httpGetStaticName(any(),any())).thenReturn("hi");
String staticName = controller.httpReturnName(eq(any())); //通过returnName()调⽤,看能否被mock
assertEquals("hi aaa", staticName);
}
/**
* 测试私有⽅法⼀
* @throws InvocationTargetException xx
* @throws IllegalAccessException xx
*/
@Test
public void testPrivateMethod() throws InvocationTargetException, IllegalAccessException {
Method method = hod(UserController.class, "say", String.class);
Object say = method.invoke(controller, "hi");
assertEquals("ljw say hi", say);
}
/**
* 测试私有⽅法⼆
* @throws Exception xx
*/
@Test
public void testPrivateMethod_2() throws Exception {
Object say = Whitebox.invokeMethod(controller, "say", "hi");
assertEquals("ljw say hi", say);
}
}
在上⾯的mockStaticMethod_3⽅法中,使⽤到了eq(),它的⽤法在注释⾥说明了,这⾥使⽤匹配器,我的理解是因为需要和这个⽅法中的第⼀个any()匹配。如果⽤实例参数,结果也是⼀样的。那如果你的是普通⽅法,就直接套⽤普通⽅法的测试⽤例。
UserController
stmock;
import javax.servlet.http.HttpServletRequest;
public final class UserController {
private final UserServiceImpl service;
public UserController(UserServiceImpl service) {
this.service = service;
}
public User saveUser(User user) {
User save = service.save(user);
return save;
}
public String returnName(){
return getStaticName("ljw1") + " aaa";
}
public String httpReturnName(HttpServletRequest request){
return httpGetStaticName("ljw1", request) + " aaa";
}
public static String getStaticName(String name) {
return "A_" + name;
}
public static String httpGetStaticName(String name, HttpServletRequest request) {
String aaa = Parameter("aaa");
return aaa + "A_" + name;
}
public String getPrivateName(String name) {
if (publicCheck()){
return "public 被mock 了";
}
if (check(name)){
return "private 被mock 了";
}
return "A_" + name;
}
public boolean publicCheck() {
return false;
}
private boolean check(String name) {
return false;
}
private String say(String content) {
return "ljw say " + content;
}
}
UserServiceImpl
public class UserServiceImpl {
public User save(User user) {
return user;
}
}
User
public class User {
private String id;
//省略set/get
}
使⽤PowerMock静态⽅法获取Context缓存中的值
有时候,我们会从缓存中去取值,⽐如登录后的⽤户,这时候,单元测试没有去启动web应⽤,没有Context的环境。这时候就让我头疼了,不知道你有没有。其实,缓存中取值也是⼀个静态⽅法。
import org.t.ContextLoader;
import javax.servlet.ServletContext;
public class CacheUtil {
public static String getRequestVal(String key){
ServletContext application = CurrentWebApplicationContext().getServletContext();
return (Attribute(key);
}
}
这就很简单了吧,直接套⽤上⾯静态⽅法的测试就可以了。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kito.PowerMockito;
import lassloader.annotations.PrepareForTest;
equals()方法
import dules.junit4.PowerMockRunner;
import static org.junit.Assert.assertEquals;
@RunWith(PowerMockRunner.class)
@PrepareForTest(CacheUtil.class)
public class CacheUtilTest {
@Test
public void getRequestVal(){
PowerMockito.RequestVal("username")).thenReturn("admin");
String username = RequestVal("username");
//可以使⽤实例值,也可以使⽤匹配器
/
/PowerMockito.RequestVal(anyString())).thenReturn("admin");
//String username = RequestVal(anyString());
assertEquals("admin", username);
}
}
普通⽅法
需要测试的⽅法
public String getVal(String key){
ServletContext application = CurrentWebApplicationContext().getServletContext(); return (Attribute(key);
}
测试
@RunWith(PowerMockRunner.class)
@PrepareForTest({CacheUtil.class,ContextLoader.class})
public class CacheUtilTest {
@Mock
private CacheUtil cacheUtil;
@Test
public void getVal(){
Mockito.Val(any())).thenReturn("admin");
String name = Val(any());//执⾏该⽅法
assertEquals("admin", name);//验证
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论