如何重构 class 以便在没有反射的情况下进行测试
How to refactor class in order to be testable without reflection
我有一个 class 类似于:
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
方法,因为它不是直接来自此 class 中任何 public 方法的 运行。这 class 设计有问题吗(从单元测试的角度来看)?
我正在使用 JMockit 进行测试...
如评论中所述-您测试
desired public observable behavior
因此,如果您想测试私有方法,则需要创建它们 public。我建议将其作为接口:
public interface SomeInterface {
changeStatus(Status status);
}
然后将实现注入到你的 class:
public final class A {
private final SomeInterface someInterface;
public A(SomeInterface someInterface) {
this.someInterface = someInterface;
}
}
然后您可以轻松地测试 SomeInterface
实现和模拟,如果您需要它 class A
。
因此,我无法为您提供针对您的特定案例的整个重构过程。但是您可以遵循此准则,并且可以使用易于测试的接口封装所有私有方法。如我所见,您在私有方法中使用了 class 的内部细节——这些细节应该封装在接口实现构造函数中(通过另一个接口),您最终会得到小而内聚的可测试 class es。浏览 Command Pattern as it seems suitable for your case and try to follow SOLID 这也将导致可测试的代码。
我发现你的设计有几个问题:
init()
方法。这会导致时间耦合,因为您的 class 在构建后尚未准备好使用;
- 您的
runCommand
方法根据状态做两件事。它要么是 运行 命令,要么将其放入地图(这是隐藏的副作用);
- 你的
changeStatus
也是 运行ning 命令。
你需要解耦那些薄薄的东西(运行ning 命令,保持它们并跟踪状态)。也许将命令的状态封装在命令本身中。因此该命令将知道如何以自己的方式工作。
我有一个 class 类似于:
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
方法,因为它不是直接来自此 class 中任何 public 方法的 运行。这 class 设计有问题吗(从单元测试的角度来看)?
我正在使用 JMockit 进行测试...
如评论中所述-您测试
desired public observable behavior
因此,如果您想测试私有方法,则需要创建它们 public。我建议将其作为接口:
public interface SomeInterface {
changeStatus(Status status);
}
然后将实现注入到你的 class:
public final class A {
private final SomeInterface someInterface;
public A(SomeInterface someInterface) {
this.someInterface = someInterface;
}
}
然后您可以轻松地测试 SomeInterface
实现和模拟,如果您需要它 class A
。
因此,我无法为您提供针对您的特定案例的整个重构过程。但是您可以遵循此准则,并且可以使用易于测试的接口封装所有私有方法。如我所见,您在私有方法中使用了 class 的内部细节——这些细节应该封装在接口实现构造函数中(通过另一个接口),您最终会得到小而内聚的可测试 class es。浏览 Command Pattern as it seems suitable for your case and try to follow SOLID 这也将导致可测试的代码。
我发现你的设计有几个问题:
init()
方法。这会导致时间耦合,因为您的 class 在构建后尚未准备好使用;- 您的
runCommand
方法根据状态做两件事。它要么是 运行 命令,要么将其放入地图(这是隐藏的副作用); - 你的
changeStatus
也是 运行ning 命令。
你需要解耦那些薄薄的东西(运行ning 命令,保持它们并跟踪状态)。也许将命令的状态封装在命令本身中。因此该命令将知道如何以自己的方式工作。