4

我写了一个相当复杂的 JavaFx 2 应用程序,我想为它写一堆单元测试。问题是当我尝试进行测试时,我收到一个运行时错误,抱怨未初始化的工具包。

据我所知,我应该以某种方式在 @BeforeClass 方法中调用 Application.launch() ,但这会导致死锁,因为 Application.launch() 不会返回调用线程。

所以问题是我应该如何初始化 JavaFx?

这是不起作用的代码骨架:

public class AppTest extends Application {

    @BeforeClass
    public void initialize() {
        launch(); //this causes a deadlock
    }

    @Test
    public void test1() {
        //conduct test here
    }

    @Test
    public void test2() {
        //conduct other test here
    }

    @Override
    public void start(Stage arg0) throws Exception {
    }

提前致谢!

4

3 回答 3

3

从stackoverflow上的另一个问题,我让自己成为了这个小助手类:

import javafx.application.Application;
import javafx.stage.Stage;

public class JavaFXInitializer extends Application {

    private static Object barrier = new Object();

    @Override
    public void start(Stage primaryStage) throws Exception {
        synchronized(barrier) {
            barrier.notify();
        }
    }

    public static void initialize() throws InterruptedException {
        Thread t = new Thread("JavaFX Init Thread") {
            public void run() {
                Application.launch(JavaFXInitializer.class, new String[0]);
            }
        };
        t.setDaemon(true);
        t.start();      
        synchronized(barrier) {
            barrier.wait();
        }
    }
}

然后可以在 @BeforeClass 设置方法中轻松使用它:

@BeforeClass
public void setup() throws InterruptedException {
    JavaFXInitializer.initialize();
}
于 2014-09-22T07:54:21.240 回答
1

主要想法是考虑您的测试在 FX 线程内运行。当您创建一个扩展应用程序的类时,您实际上创建了一个进程。这就是您要测试的内容。

因此,要在应用程序上启动一些单元测试,首先创建一个扩展应用程序的 FXAppTest,然后在 FXAppTest 中启动单元测试。这是想法。

这是一个使用 JUnit 的示例。我创建了一个 Runner,它在 FXApp 中启动测试以进行测试。这是 FxApplicationTest 的代码示例(我们在其中启动单元测试)

 public class FxApplicationTest extends Application {

    private volatile boolean isStopped;

    @Override
    public void start(final Stage stage) {
        StackPane root = new StackPane();
        Scene scene = new Scene(root, 10, 10);
        stage.setScene(scene);
    }

    public void startApp() {
        launch();
    }

    public void execute(final BlockJUnit4ClassRunner runner, final RunNotifier notifier) throws InterruptedException {
        isStopped = false;
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                runner.run(notifier);
                isStopped = true;
            }
        });
        while (!isStopped) {
            Thread.sleep(100);
        }
    }

和亚军:

import org.apache.log4j.Logger;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;

public class JUnitFxRunner extends Runner {

    private final BlockJUnit4ClassRunner runner;
    private final Logger LOGGER = Logger.getLogger(JUnitFxRunner.class);

    public JUnitFxRunner(final Class<?> klass) throws InitializationError {
        super();
        runner = new BlockJUnit4ClassRunner(klass);
    }

    @Override
    public Description getDescription() {
        return Description.EMPTY;
    }

    @Override
    public void run(final RunNotifier notifier) {
        try {

            final FxApplicationTest fxApplicationTest = new FxApplicationTest();

            MyTestRunner runnable = new MyTestRunner(runner, notifier, fxApplicationTest);
            new Thread(runnable).start();
            Thread.sleep(100);
            runnable.execute();
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }

    }

    private class MyTestRunner implements Runnable {

        private final BlockJUnit4ClassRunner runner;
        private final RunNotifier notifier;
        private final FxApplicationTest fxApp;

        public MyTestRunner(final BlockJUnit4ClassRunner runner, final RunNotifier notifier, final FxApplicationTest fxApp) {
            this.runner = runner;
            this.notifier = notifier;
            this.fxApp = fxApp;
        }

        @Override
        public void run() {
            fxApp.startApp();
        }

        public void execute() throws InterruptedException {
            fxApp.execute(runner, notifier);
        }
    }

}

现在,只需使用 runner 启动测试:

import fr.samarie_projects.fx.utils.JUnitFxRunner;

@RunWith(JUnitFxRunner.class)
public class MainFxAppTest {

    @org.junit.Test
    public void testName() throws Exception {
        MainFxApp fxApp = new MainFxApp();
        fxApp.start(new Stage());
    }

}

本单元测试 MainFxApp

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class MainFxApp extends Application {

    @Override
    public void start(final Stage stage) throws Exception {
        StackPane root = new StackPane();
        Scene scene = new Scene(root, 10, 10);
        stage.setScene(scene);
    }

    public static void main(final String[] args) {
        launch(args);
    }

}

当然,需要审查此代码。它只是提出这个想法。

于 2013-05-16T12:41:31.690 回答
0

好吧,考虑到您的 JavaFX 应用程序可能位于project-root/src/main/java/package/FXApp.java ,那么您的测试可能位于其他位置,例如 project-root/src/test/java/package/FXAppTest。爪哇 _ 在这种情况下,FXAppTest 类可以通过使用 BeforeClass 对其进行初始化来调用 FXApp 类。

从理论上讲,您应该能够使用以下内容启动您的 FX 应用程序:

// imports located here that import junit 4.11+ packages (or TestNG)
public class FXAppTest {
@BeforeClass
public void initialize() {
   FXApp fxa = new FXApp();
   while ( fxa.isLoading() ) {
      // do nothing
   }
}
....

注意:请注意,FXAppTest 不会在此处扩展 Application。

现在,如果这不能提示您解决问题,您可以在 JVM 上启用 JMX args,然后使用 JVisualVM 查看锁定的线程。

于 2012-11-20T21:10:55.827 回答