我目前正在开发一个简单的JavaFX MVC/MVP来测试我是否可以让JavaFX的任何单元测试工作。我的目标是在单元测试中测试JavaFX对话框或控件。为了使我的单元测试尽可能独立于任何JavaFX调用,我想尝试模拟任何JavaFX调用。但不幸的是,我正在努力让它正常工作。我也不想使用TestFX,因为缺乏适当的文档。
到目前为止,我对Java中的单元测试还是有点陌生,但我确实有用c++编写单元测试的经验(google test/mock)
规格:
- JDK 8
- Mockito 3.5.13
- JUnit 4.12
- Hamcrest 1.3
到现在为止我有什么?
我已经创建了一个View类,它负责更新对话框,注册按钮的操作以及显示对话框。
public class View {
private Stage stage;
private Label label;
private Button button;
public View(Stage stage, Label label, Button button) {
this.stage = stage;
this.label = label;
this.button = button;
}
public void updateLabel(int count) {
label.setText("Countdown: " + count);
}
public void ShowStage() {
//button.setText("Count");
//stage.setScene(new Scene(new VBox(label, button)));
stage.show();
}
public void RegisterButtonAction(EventHandler<ActionEvent> value) {
button.setOnAction(value);
}
}
此外,我还有一个视图的单元测试,其中有三个测试:
- 对于构造函数
- 用于更新标签
- 对于这个单元测试,我已经有了一个JavaFX线程规则(参见How do you mock a JavaFX toolkit initialization?),它负责启动一个JavaFX线程。对于我用mockito创建的按钮、标签和stage,它们是通过构造函数注入的(用于测试的快速破解)。当我创建模拟时,我希望对JavaFX的调用被模拟,但似乎仍然缺少一些东西。
public class ViewTest {
@org.junit.Rule
public JavaFXThreadingRule rule = new JavaFXThreadingRule();
private Stage m_stageMock;
private Label m_labelMock;
private Button m_buttonMock;
private View m_testee;
@Before
public void SetUp() throws InterruptedException {
m_stageMock = mock(Stage.class);
m_labelMock = mock(Label.class);
m_buttonMock = mock(Button.class);
m_testee = new View(m_stageMock, m_labelMock, m_buttonMock);
}
@After
public void TearDown(){
m_testee = null;
m_stageMock = null;
m_labelMock = null;
m_buttonMock = null;
}
@Test
public void Constructor_HappyPath_NoCrash() {
}
@Test
public void UpdateLabel_IntegerGiven_PrintNumber() {
int count = 8;
doNothing().when(m_labelMock).setText("Countdown: " + count);
m_testee.updateLabel(count);
}
@Test
public void ShowStage_HappyPath_ShowsWindowWithButtonAndLabel() {
VBox vbox = new VBox();
Scene scene = new Scene(vbox);
//doNothing().when(m_buttonMock).setText("Count");
//doNothing().when(m_stageMock).setScene(any(Scene.class));
doNothing().when(m_stageMock).show();
m_testee.ShowStage();
}
}
到目前为止,我还尝试了哪些其他东西?
- 我尝试将mockito设置为能够模拟final函数/类。
- 我尝试实现一个基于How do you mock a JavaFX toolkit initialization?的自定义mock maker。
也许有人有一个解决方案或一个例子给我,或者我只是错过了一些小的东西。如果我能得到任何帮助,我将不胜感激。
所有最好的
窝
1条答案
按热度按时间64jmpszr1#
我希望我的回答能对你有帮助,因为你已经很久没有问过这个问题了,但它可以帮助别人。如果你分享你的测试输出,不管有没有错误,这都会有所帮助,但是我之前也是这样。首先,您需要了解JavaFX、Mockito和JUnit平台是如何工作的。它们是框架,在Java虚拟机中执行的方式是独特的。任何属于JavaFX领域的工件都需要JavaFX的运行时,然后才能按照您的预期方式工作。这就是为什么main类(包含main方法的类)必须子类化JavaFX的Application类并覆盖它的“start”方法。当你的应用程序运行时,java知道要寻找这个方法,并且你的应用程序在JavaFX的域中运行,允许所有JavaFX的工件在你的代码中执行。换句话说,一个特定于JavaFX的线程将被启动,这就是你的应用程序将被执行的地方,远离主线程。其次,当您使用Junit运行测试时,您不需要创建一个主类及其main方法。Junit的框架在后台为您完成了这一工作,并执行了您的测试。Junit不知道JavaFX,所以你的测试应该失败,并抛出IllegalStateException,因为JavaFX线程还没有启动。Junit不会自动完成这一任务。需要启动JavaFX的主线程,所有JavaFX代码都需要在该线程上运行。在我的情况下,我需要测试我的JavaFX应用程序中的一些业务逻辑,这些是应用程序中不需要JavaFX工件的部分。我有一个这样的
你可以看到调用GUI来显示的部分,那是JavaFX代码。这个方法在一个单独的线程上运行,因为我在应用程序中使用了并发,当我需要从一个不是主JavaFX线程的线程调用JavaFX代码(比如更新GUI组件)时,我必须将我的语句 Package 在Runnable中,将其交给“runLater”方法,它会在JavaFX的线程上安全地执行它们。在这里阅读关于它。如前所述测试此方法将在JavaFx的线程未运行时抛出IllegalStateException。因此,为了将JavaFX框架引入到我的测试中,我不得不在我的测试中添加下面这行代码。
你只需要简单地将你的测试放在可运行对象中,并在你的测试方法中调用那一行。这将JavaFX框架引入到您的测试中,并且它的工件会按照它们应该的方式执行,而Junit不会以任何方式受到损害。你可以在这里的JavaFX 17文档中阅读关于这个类的内容。这是对我有效的测试
JUnit将调用JavaFX框架,允许您顺利执行测试。因此,请始终记住,所有JavaFX工件都应该在JavaFX的线程上执行或调用,而不是在任何其他线程上执行或调用。调用
Platform.startup(runnable);
将确保JavaFX的主线程启动,或者使您的主类成为Application的子类,如果您必须从非JavaFX线程的线程执行任何JavaFX,请使用Platform.runLater(Runnable runnable)