单元测试实战——反射在单元测试中的应用

2,944 阅读2分钟

一、反射

1.反射的定义

是指程序在运行状态中,可以动态获取对象的信息,动态使用对象的属性和方法。

2.详解

反射的强大之处在于,可以跳过java代码的private权限控制。即可以给private且没有set方法的属性赋值,直接调用private修饰的方法。

二、使用场景

如果想测试一个私有方法,这里有几种解决方案

  • 把私有方法权限变为protected或public
  • 测试外层的public方法
  • 使用反射直接调用私有方法进行测试

首先第一种方案是非常不建议使用的,相当于为了做单元测试修改了原代码,这个方法本来的意义改变了,容易使其他开发同事误解,降低了可读性。

第二种方法,如果逻辑复杂,多个private方法嵌套时,直接调用最外层的public方法就不是一个好的选择,单元测试代码会很混乱,不易维护,一些分支逻辑也不容易被覆盖到,单元测试覆盖率不足。

第三种方法是最可取的,相当于在测试时改变了方法的权限,可以直接在测试类中调用,对源代码又没有任何侵入。

三、一些例子

0.测试类

public class Server {
    
    private Resource resource = new ClassPathResource("webapp");
    
    public Server() {
        // ...
    }
    
    private boolean isPathExist(String path) {
        if (path == null) return false;
        if ("".equals(path)) return false;
        return true;
    }
    
    private void setUrl(WebAppContext webapp) {
        try {
            webapp.setResourceBase(resource.getURI().toString());
        } catch (IOException e) {
            // ...
        }
    }
    
}

1.使用反射调用私有方法

测试私有方法isPathExist

@Test
public void test1() {
    Server server = Mockito.mock(Server.class); // mock一个server对象
    Method method = Server.class.getDeclaredMethod("isPathExist", String.class); // 通过反射获取要调用的私有方法对象
    method.setAccessible(true); // 把私有方法的访问权限设置为true
    Boolean bel1 = (Boolean)method.invoke(server, ""); // 调用私有方法
    Assert.assertFalse(bel1);
    Boolean bel2 = (Boolean)method.invoke(server, "xxx");
    Assert.assertTrue(bel2);
}

2.使用反射修改私有变量

修改私有变量resource

@Test
public void test2() {
    Server server = Mockito.mock(Server.class);// mock一个server对象
    Field fieldResource = Server.class.getDeclaredField("resource"); // 通过反射获取私有属性对象
    fieldResource.setAccessible(true); // 设置访问权限为true
    Resource resource = Mockito.mock(ClassPathResource.class); // mock要注入的对象
    // ...
    fieldResource.set(server, resource); // 把这个mock的resource注入进server对象中
    // ...
}

四.ReflectionTestUtils

1.基本概念

org.springframework.test.util.ReflectionUtils是spring-test提供的一个反射工具类,封装了Java反射API的一些常用操作,可以更加方便在测试中实现反射功能

2.ReflectionTestUtils调用私有方法

测试私有方法isPathExist
语法:ReflectionTestUtils.invokeMethod(Object target, String name, Object ... args)

@Test
public void test1() {
    Server server = Mockito.mock(Server.class); // mock一个server对象
    Boolean bel1 = ReflectionTestUtils.invokeMethod(server, "isPathExist", "");
    Assert.assertFalse(bel1);
    Boolean bel2 = ReflectionTestUtils.invokeMethod(server, "isPathExist", "xxx");
    Assert.assertTrue(bel2);
}

3.ReflectionTestUtils修改私有变量

修改私有变量resource
语法:ReflectionTestUtils.setField(Object targetObject, String name, Object value)

@Test
public void test2() {
    Server server = Mockito.mock(Server.class);// mock一个server对象
    Resource resource = Mockito.mock(ClassPathResource.class); // mock要注入的对象
    ReflectionTestUtils.setField(server, "resource", resource);
    // ...
}

五、总结

反射工具类ReflectionTestUtils配合Mockito使用,可以大大提高单元测试编写效率