如何重构类以便在没有反射的情况下进行测试

时间:2017-08-24 07:49:51

标签: java unit-testing jmockit

我有一个类似的课程:

public class QueueingCommandRunner {
    private Status status;
    private Map<Class<CommandHandler>, CommandHandler> queuedCommands;
    private RunnerClass runnerClass;
    private ExternalCommandRunnerRegistry externalCommandRunnerRegistry;
    private ExternalCommandRunner externalCommandRunner;

    public QueueingCommandRunner(ExternalCommandRunnerRegistry externalCommandRunnerRegistry,
            RunnerClass runnerClass) {

        this.externalCommandRunnerRegistry = externalCommandRunnerRegistry;
        this.runnerClass = runnerClass;
        this.queuedCommands = new LinkedHashMap<>();
        this.status = Status.DOWN;
    }

    public void init() {
        doSomeStuff();
        externalCommandRunner = externalCommandRunnerRegistry.get(runnerClass);
        externalCommandRunner.runListeningCommand(ListenableStatusCommand.class,
                new ListenableStatusHandler(this::changeStatus));
    }

    public <T extends CommandHandler> void runCommand(Class<T> command, T commandHandler) {
        if (status == UP) {
            externalCommandRunner.run(command, commandHandler);
        } else {
            queuedCommands.put(command, commandHandler);
        }
    }

    private void changeStatus(Status status) {
        this.status = status;
        if (status == UP) {
            Iterator<Entry<Class<CommandHandler>, CommandHandler>> commandsIterator =
                queuedCommands.iterator();
            while (commandsIterator.hasNext()) {
                <Entry<Class<CommandHandler>, CommandHandler>> queuedCommand = commandsIterator.next();
                externalCommandRunner.run(queuedCommand.getKey(), queuedCommand.getValue());
                commandsIterator.remove();
            }
        }
    }
}

我省略了同步等内容。我的问题是,如何测试内部的排队而不使用通过反射调用私有方法之类的东西?特别是我想知道如何测试changeStatus方法,因为它不是直接从这个类中的任何公共方法运行的。这个类是否设计不合理(从单元测试的角度来看)?

我正在使用JMockit进行测试......

1 个答案:

答案 0 :(得分:1)

正如评论中提到的那样 - 你测试

  

期望的公众可观察行为

因此,如果您想测试私有方法,则需要将它们公开。我建议把它作为界面:

public interface SomeInterface {

    changeStatus(Status status);
}

然后向您的班级注入实施:

public final class A {

    private final SomeInterface someInterface;

    public A(SomeInterface someInterface) {
        this.someInterface = someInterface;
    }
}

然后,您可以轻松测试SomeInterface实施,并在课程A中模拟您是否需要。

所以,我无法为您的特定情况提供整个重构过程。但您可以遵循此准则,并且可以使用易于测试的接口封装所有私有方法。正如我所看到的,您正在私有方法中使用类的内部细节 - 这些细节应该封装在接口实现构造函数中(通过另一个接口),最终会得到一些小的,有凝聚力的可测试类。仔细查看Command Pattern,因为它似乎适合您的情况,并尝试关注SOLID,这也将导致可测试的代码。

我发现您的设计存在一些问题:

  • init()方法。这导致了时间耦合,因为你的班级在建造后还没准备好使用;
  • 您的runCommand方法根据状态执行两项操作。它既可以运行命令,也可以将其映射到地图(这是隐藏的副作用);
  • 您的changeStatus也在运行命令。

你需要解耦那些(运行命令,保持它们和跟踪状态)。也许在命令本身内封装命令的状态。因此命令将知道如何以自己的方式工作。