gpt4 book ai didi

java - 如何测试 JavaFX (MVC) Controller 逻辑?

转载 作者:塔克拉玛干 更新时间:2023-11-02 19:12:35 28 4
gpt4 key购买 nike

我们如何正确地为 JavaFX Controller 逻辑编写单元/集成测试? 假设我正在测试的 Controller 类名为 LoadController,它的单元测试类是 LoadControllerTest,我的困惑源于:

  • 如果 LoadControllerTest 类通过实例化一个新的 LoadController 对象LoadController loadController = new LoadController(); 我可以然后通过(许多) setter 将值注入(inject) Controller 。这似乎是不使用反射(遗留代码)的唯一方法。如果我不将值注入(inject) FXML 控件,那么控件显然还没有初始化,返回 null。

  • 如果我改为使用 FXMLLoaderloader.getController() 方法来检索 loadController,它将正确初始化 FXML控制但因此调用了 Controller 的 initialize(),这导致运行非常缓慢,并且由于无法注入(inject)模拟的依赖项,因此它更像是一个编写不当的集成测试。

我现在使用的是前一种方法,但是有更好的方法吗?

测试FX

答案here涉及 TestFX,它具有基于主应用程序的 start 方法的 @Tests 不是 Controller 类。它显示了一种使用

测试 Controller 的方法
     verifyThat("#email", hasText("test@gmail.com"));

但这个答案涉及 DataFX - 而我只是询问 JavaFX 的 MVC 模式。大多数 TestFX 讨论都集中在它的 GUI 功能上,所以我很好奇它是否也适用于 Controller 。

以下示例展示了我如何将 VBox 注入(inject) Controller ,以便它在测试期间不为空。有没有更好的办法?请具体点

 public class LoadControllerTest {

@Rule
public JavaFXThreadingRule javafxRule = new JavaFXThreadingRule();

private LoadController loadController;
private FileSorter fileSorter;
private LocalDB localDB;
private Notifications notifications;
private VBox mainVBox = new VBox(); // VBox to inject

@Before
public void setUp() throws MalformedURLException {
fileSorter = mock(FileSorter.class); // Mock all dependencies

when(fileSorter.sortDoc(3)).thenReturn("PDF"); // Expected result

loadController = new LoadController();
URL url = new URL("http://example.com/");
ResourceBundle rb = null;
loadController.initialize(url, rb); // Perhaps really dumb approach
}

@Test
public void testFormatCheck() {
loadController.setMainVBox(mainVBox); // set value for FXML control
assertEquals("PDF", loadController.checkFormat(3));
}
}

public class LoadController implements Initializable {

@FXML
private VBox mainVBox; // control that's null unless injected/instantiated

private FileSorter fileSorter = new FileSorter(); // dependency to mock

@Override
public void initialize(URL location, ResourceBundle resources) {
//... create listeners
}

public String checkFormat(int i) {
if (mainVBox != null) { // This is why injection was needed, otherwise it's null
return fileSorter.sortDoc(i);
}
return "";
}

public void setMainVBox(VBox menuBar) {
this.mainVBox = mainVBox; // set FXML control's value
}

// ... many more setters ...
}

更新

这是一个基于 hotzst 的建议的完整演示,但它返回了这个错误:

org.mockito.exceptions.base.MockitoException: Cannot instantiate @InjectMocks field named 'loadController' of type 'class com.mypackage.LoadController'. You haven't provided the instance at field declaration so I tried to construct the instance. However the constructor or the initialization block threw an exception : null

import javafx.scene.layout.VBox;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class LoadControllerTest {

@Rule
public JavaFXThreadingRule javafxRule = new JavaFXThreadingRule();
@Mock
private FileSorter fileSorter;
@Mock
private VBox mainVBox;
@InjectMocks
private LoadController loadController;

@Test
public void testTestOnly(){
loadController.testOnly(); // Doesn't even get this far
}
}

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.VBox;
import java.net.URL;
import java.util.ResourceBundle;

public class LoadController implements Initializable {

private FileSorter fileSorter = new FileSorter(); // Fails here since creates a real object *not* using the mock.

@FXML
private VBox mainVBox;

@Override
public void initialize(URL location, ResourceBundle resources) {
//
}

public void testOnly(){
if(mainVBox==null){
System.out.println("NULL VBOX");
}else{
System.out.println("NON-NULL VBOX"); // I want this to be printed somehow!
}
}
}

最佳答案

您可以使用类似 Mockito 的测试框架在 Controller 中注入(inject)您的依赖项。因此,您可能可以放弃大部分 setter ,至少可以放弃那些仅用于促进测试的 setter 。

根据您提供的示例代码,我调整了被测类(为 FileSorter 定义了一个内部类):

public class LoadController implements Initializable {

private FileSorter fileSorter = new FileSorter();

@FXML
private VBox mainVBox;

@Override
public void initialize(URL location, ResourceBundle resources) {
//
}

public void testOnly(){
if(mainVBox==null){
System.out.println("NULL VBOX");
}else{
System.out.println("NON-NULL VBOX");
}
}

public static class FileSorter {}
}

@FXML 注释在这里没有任何意义,因为没有附加 fxml 文件,但它似乎对代码或测试没有任何影响。

您的测试类可能看起来像这样:

@RunWith(MockitoJUnitRunner.class)
public class LoadControllerTest {

@Mock
private LoadController.FileSorter fileSorter;
@Mock
private VBox mainVBox;
@InjectMocks
private LoadController loadController;

@Test
public void testTestOnly(){
loadController.testOnly();
}
}

此测试成功运行并产生以下输出:

NON-NULL VBOX

@Rule JavaFXThreadingRule 可以省略,因为当像这样测试时,您不会运行应该在 JavaFX 线程中执行的代码的任何部分。

@Mock 注释与 MockitoJUnitRunner 一起创建一个模拟实例,然后将其注入(inject)到使用 @InjectMocks 注释的实例中。

可以找到一个很好的教程here .还有其他用于模拟测试的框架,例如 EasyMockPowerMock , 但 Mockito 是我使用和最熟悉的。

我将 Java 8 (1.8.0_121) 与 Mockito 1.10.19 一起使用。

关于java - 如何测试 JavaFX (MVC) Controller 逻辑?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41732654/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com