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

一、反射

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 bel1 = (Boolean)method.invoke(server, "xxx");
    Assert.assertTrue(bel1);
}
复制代码

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

修改私有变量resource,测试setUrl方法

@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要注入的对象
    Mockito.doThrow(new IOException()).when(resource).getURI();// mock要注入对象的行为,调用getURI方法时会抛出IOException
    fieldResource.set(server, resource); // 把这个mock的resource注入进server对象中
    WebAppContext webapp = new WebAppContext();
    // 调用需要测试的方法
    Method method = Server.class.getDeclaredMethod("setUrl", WebAppContext.class);
    method.setAccessible(true);
    method.invoke(server, webapp);
    Assert.assertNull(webapp.getResourceBase());
}
复制代码

四、总结

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

分类:
后端