使用java反射来单元测试在方法实现中使用特定的类

时间:2017-07-09 14:53:31

标签: java unit-testing reflection

我想实现并单元测试使用Java 8 Stream API(Stream接口和Collectors类)聚合某些数据的方法。请参阅以下代码:

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class AggregationKata {

    public static Map<String, Double> getAverageGradeByDepartment(Stream<Student> students) {

        return students
                .collect(Collectors.groupingBy(Student::getDepartment,
                        Collectors.averagingDouble(Student::getGrade)));

    }
}

我想编写一个单元测试,在实现此方法时强制使用Stream API。换句话说,必须使用Stream接口和Collectors类。我想我需要在单元测试中使用java反射,但我无法弄清楚如何。

问题是 - 我该如何编写这样的单元测试?谢谢!

2 个答案:

答案 0 :(得分:1)

您想要做的不是单元测试 你不想测试你班级的行为 您希望确保实现遵循特定的编码规则。

要解决此要求,您应该在每次提交时在Jenkins中处理它的工具(例如Sonar)或甚至是静态代码分析工具中定义规则。

通过在Sonar中实现自定义规则,您可以使用反射和检查源代码,但不能直接使用(API为您完成工作)。

例如:

@Override
public void visitNode(Tree tree) {
  MethodTree method = (MethodTree) tree;
  if (method.parameters().size() == 1) {
    MethodSymbol symbol = method.symbol();
    reportIssue(method.simpleName(), "Never do that!");
  }
}

答案 1 :(得分:0)

Reflection无法访问方法体。所以它不能用于强制执行特定的实现。

分析方法体的唯一方法是源代码分析(解析器+约束),或者如果代码已经编译了字节码分析。

所以使用java的解析器并实现你想要的必要约束;或使用asm等字节码工具。

在我看来,字节码分析比源代码更容易,所以我会使用asm。

这里有一个代表性的原始示例,代码将为您的示例打印[java/util/stream/Collectors::groupingBy, java/util/stream/Stream::collect, java/util/stream/Collectors::averagingDouble]。 (需要org.ow2.asm:asm:jar:5.2)

public static class MethodsCalledClassVisitor extends ClassVisitor {
    private Set<String> methodsCalled = new HashSet<>();

    public MethodsCalledClassVisitor() {
        super(Opcodes.ASM5);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if ("getAverageGradeByDepartment".equals(name)) {
            return new MyMethodVisitor(this);
        } else {
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
    }

    public void addMethodCalled(String methodCall) {
        methodsCalled.add(methodCall);
    }

    public Set<String> getMethodsCalled() {
        return methodsCalled;
    }
}

public static class MyMethodVisitor extends MethodVisitor {
    private final MethodsCalledClassVisitor visit;

    public MyMethodVisitor(MethodsCalledClassVisitor visit) {
        super(Opcodes.ASM5);
        this.visit = visit;
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
        visit.addMethodCalled(owner + "::" + name);
        super.visitMethodInsn(opcode, owner, name, desc, itf);
    }

}

public static void main(String[] args) throws IOException {
    ClassReader reader = new ClassReader(AggregationKata.class.getName());

    MethodsCalledClassVisitor classVisitor = new MethodsCalledClassVisitor();
    reader.accept(classVisitor, 0);
    System.out.println(classVisitor.getMethodsCalled());
}