接口里的默认方法,算是Ja va 8带来的一个相当实用的特性。不过,真要给它写单元测试的时候,不少同学可能会愣一下——这玩意儿不能直接new出来测啊。
说到底,核心思路其实很简单:默认方法不能自己跑,得靠它的实现类来测。咱们要测的不是接口的定义本身,而是具体实现类在继承或者重写默认方法之后,到底跑出了什么行为。
用实现类调用默认方法进行测试
Ja va接口的default方法,终究得由实现类的对象来调用才走得通。写JUnit测试的时候,做法就是先搞一个接口的实现类——可以是匿名内部类、私有静态类,或者直接用现成的具体类——然后通过这个对象去调用方法,验证逻辑对不对。
- 比较推荐的做法是整一个私有的静态内部类来实现接口,这样不会污染生产代码
- 如果接口已经有一个具体的实现类了(比如叫
ServiceA),那就直接拿来复用,省事 - 有一点必须注意:不能对着接口类型变量直接
new,必须有实实在在的实现类
测试默认方法被子类重写的情况
假如实现类把默认方法给覆盖了,那测试就得覆盖两条路:没重写的时候走默认逻辑,重写了之后走新逻辑。
- 对于没重写的情况,用个空实现类,或者只继承不覆写的类就行
- 对于重写的情况,要确保实现类里明确写了
@Override,并且行为确实变了 - 可以分别构造两个不同的实现对象,然后对比断言的结果,这样一目了然
注意默认方法中的依赖与副作用
真正的坑,往往不在方法本身,而在它调用了什么、产生了什么影响。如果默认方法里调用了其他方法——比如静态工具类、或者this引用的其他默认方法——或者有副作用,比如写日志、改状态,那就得专门去验证。
- 如果依赖外部对象(比如
Logger、Config),可以通过实现类注入,或者mock掉它的调用链 this调用其他默认方法的时候,测试仍然走完整的实现类对象就行,不需要特殊处理- 最好别在默认方法里塞复杂的业务逻辑或者I/O操作——不然测试起来真的会头疼
示例:简单可运行的测试片段
假设接口是这样定义的:
public interface Greeting {
default String sayHello(String name) {
return "Hello, " + name;
}
}对应的测试写法是这样的:
@Test
void sayHello_returnsFormattedGreeting() {
Greeting greeting = new Greeting() {}; // 匿名实现
assertEquals("Hello, Alice", greeting.sayHello("Alice"));
}更规范一点的做法,是定义一个静态内部类:
private static class TestGreeting implements Greeting { }
// 然后直接 new TestGreeting().sayHello(...) 就行了
