其实认真写测试开始,主要面向的是 Python 这样没有访问修饰符的语言,并且考 虑到动态自由的特性,所以测试内部方法甚至动态的进行内容替换都是相对简单的 事情。以至于…笔者一直没留意到还有访问修饰符这样基础的问题会挡在测试面前。
从设计上来说,的确私有方法的目的是在类内部使用,外部不必知道细节。但既然写 自动测试是从 白盒测试 角度看待问题,那就表示需要看到内部,所以笔者倾向于 对测试内部方法也进行针对性测试;尤其是当逻辑复杂到一定规模时一定会多拆几个 方法时。
如何在 Java 中测试私有方法
不用 private 等访问修饰符
是的,看似很愚蠢但是…确实也是一种办法,但是在生产项目中这种办法是不实际 的,违背了封装原则。但如果没有掌握其他办法前,也不失为一种 临时方案 。
增加对应的 public 方法暴露测试接口
可以给类添加对应的 public
方法,一对一的暴露私有方法。测试的时候调用暴露
给测试的接口。优点看上去比较清晰,不过总体来说,带来的缺点更严重一些,私有
方法调整的时候还需要调整对应的 public
接口,并且多了一层额外的维护负担。
其他类似的套路还有:将过多的私有方法封装到单独的协助类里,这样就必须暴露公 用访问接口,这里不再赘述。
使用反射机制测试私有方法
反射机制很强大,可以拿到运行时的绝大多数信息,可以借此绕开访问权限的检查。
// src/java/main/MyExample.java
public class MyExample {
private String internalThing() {
return "some secret";
}
}
// src/test/java/TestMyExample.java
public class TestMyExample {
@Test
public void testInternal()
throws NoSuchMethodException,
InvocationTargetException, IllegalAccessException{
// 创建一个实例,用于调用
MyExample inst = new MyExample();
// 反射机制获取方法
Class<MyExample> clazz = MyExample.class;
// 注意这里的用法,翻一下 reflect 的文档
Method method = clazz.getDeclaredMethod("internalThing");
// 修改访问控制
method.setAccessible(true);
// 调用,同样注意参数用法
Object rv = method.invoke(inst);
// 用完记得改回去
method.setAccessible(false);
}
}
在测试时,用反射可以巧妙地绕开访问控制检查;既然测试代码与被测试方法本来 就是耦合的,并没有增加额外的维护负担;如果认为精确到参数耦合大的话,一个类 的内部使用面对的问题是一样的,得约定好,尽量不要经常大的改动。
getDeclaredMethod
限制
2019.04.03 补充,这个问题发生在笔者将需要测试的方法移到父级类后出现,针对子类测试时 发现抛异常。
特别需要注意一下该方法的使用情景,该方法只能获取 类本身 的方法,即如果方法是从
父级继承过来,会报 NoSuchMethodException
异常。例如以下例子:
class MyAbstract {
private String internalThing() {
return "some secret";
}
}
class MyExample extends MyAbstract {
}
// src/test/java/TestMyExample.java
public class TestMyExample {
@Test
public void testInternal()
throws NoSuchMethodException,
InvocationTargetException, IllegalAccessException{
// 创建一个实例,用于调用
MyExample inst = new MyExample();
// 反射机制获取方法
Class<MyExample> clazz = MyExample.class;
// 注意这里的用法,翻一下 reflect 的文档
// 这里必须知道方法在类之间的关系,获取父级类后再反射方法
Method method = clazz.getSuperClass().getDeclaredMethod("internalThing");
// 修改访问控制
method.setAccessible(true);
// 调用,同样注意参数用法
Object rv = method.invoke(inst);
// 用完记得改回去
method.setAccessible(false);
}
}
而 getMethod
方法为何不能用,是因为后者仅返回 public
成员方法,与需求不符。
文档上专门提到了
See The Java™ Language Specification: 8.2 Class Members, 8.4 Method Declarations
(顺带一则) 如何在 JUnit 下运行指定测试类与测试方法
这个需求很明确,本地开发的时候针对个别测试用例编码过程中总会磕磕碰碰,需要 不断调试反复运行。如果每次都必须测试整个项目,想想那编译,启动,运行测试的 时间消耗,也是极其不科学的。如果有100个测试,而开发时只针对1个进行修改,那 每次都全量测试,时间、运算量成本一般会有超过9成浪费再不关注的点上。
很幸运 maven surefire 插件自带这样的功能
$ mvn test -Dtest='TestClass#testMethod'
甚至还有匹配功能,更多可以参考文档1。
如果没有使用 maven ,而是其他构建工具,可以参考一下对应的工具是否有插件。
最后是 JUnit 本身的大招2,需要自己加一段 Java 代码来做,实际工程中 使用的时候,一般还是尽量考虑给构建工具加入插件或者流程,更便于使用与扩展。
问题拓展
-
Java 里是如何实现类的成员访问控制的?
参考
-
https://softwareengineering.stackexchange.com/questions/100959/how-do-you-unit-test-private-methods
-
http://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html
\_\_END\_\_