我想测试一个在TornadoFX中创建新视图的函数。但是,当我调用该函数时,出现此错误。
java.lang.ExceptionInInitializerError
at tornadofx.ControlsKt.button(Controls.kt:190)
at tornadofx.ControlsKt.button$default(Controls.kt:190)
at view.PeopleMenuView$setupTopBox$1$1.invoke(PeopleMenuView.kt:33)
at view.PeopleMenuView$setupTopBox$1$1.invoke(PeopleMenuView.kt:8)
at tornadofx.LayoutsKt.vbox(Layouts.kt:388)
at tornadofx.LayoutsKt.vbox$default(Layouts.kt:103)
at view.PeopleMenuView$setupTopBox$1.invoke(PeopleMenuView.kt:31)
at view.PeopleMenuView$setupTopBox$1.invoke(PeopleMenuView.kt:8)
at tornadofx.LayoutsKt.hbox(Layouts.kt:384)
at tornadofx.LayoutsKt.hbox$default(Layouts.kt:96)
at view.PeopleMenuView.setupTopBox(PeopleMenuView.kt:29)
at view.PeopleMenuView.<init>(PeopleMenuView.kt:15)
at presenter.MainMenuPresenter.managePeoplePressed(MainMenuPresenter.kt:11)
at presenter.TestMainMenuPresenter.testManagePeoplePressed(TestMainMenuPresenter.kt:16)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:550)
at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:512)
at javafx.scene.control.Control.<clinit>(Control.java:87)
... 36 more
这是因为在函数中创建了视图的新实例。简化的代码如下所示:
fun managePeoplePressed() {
view.replaceWith(PeopleMenuView())
}
当我从测试中调用方法时,出现错误。我在Google上四处搜寻,但是没有太多的发现。
我希望能够测试创建视图的方法。谢谢。
答案 0 :(得分:2)
通常:您不想测试UI视图。紧紧,我仍然要告诉你如何。我只想让您知道这是一种罕见的情况,可以说明设计问题。
没有TestFx,这样的测试很难编写。使用TestFx,它们更容易,但仍然非常慢且非常脆弱,并且您必须在任何标准CI环境中采取额外的步骤,才能使其在通常不具备运行JavaFx功能的构建箱中运行并且没有配备虚拟显示器。
您(和TestFx)遇到的最大问题是将线程正确地放入测试中。测试是在一个线程上进行的。 JavaFx的可视部分在另一个上。您自己的应用程序和JavaFx本身经常将其任务倒入Platform.RunLater()中,如果不计入该队列的清空,您将得到的结果要么都是错误的,要么是闪烁的。睡眠在某些情况下有效,但是a)睡眠会使您减速,并且b)在较慢的机器(例如云中配置较低的Windows机器)上运行时,效果不佳。
回到您的一般问题:这可能意味着您的视图内连接很复杂,其中UI组件X依赖于UI组件Y。广义上,您希望UI组件X依赖于Model中的属性,并且您希望UI组件Y依赖于模型中的属性,并且您希望模型处理属性之间的复杂交互。 TornadoFx为此提供了直接支持,并且Model类不需要测试运行的UI。在某些情况下,控制器是放置互连逻辑的地方,但这是相对罕见的。我所做的大部分工作都不需要这样做,但是确实需要Model类。在TornadoFx的ViewModel和ItemViewModel类中,(模型!=域)得到了很好的支持。
说了这么多,如果您需要对视图进行机器人控制,则可以使用TestFx。如果您不同意,那么这是对埃德文的答案的同意并加以详细阐述,就是要有这样的东西:
class UiTest {
companion object {
private var javaFxRunning: Boolean = false
fun start() {
StartWith.isUi = true
Errors.reallyShow = false
try {
runJavaFx()
} catch (e: InterruptedException) {
throw RuntimeException(e)
}
}
@Throws(InterruptedException::class)
private fun runJavaFx() {
if (javaFxRunning) return
val latch = CountDownLatch(1)
SwingUtilities.invokeLater {
JFXPanel()
latch.countDown()
}
latch.await()
javaFxRunning = true
}
}
}
在@BeforeEach甚至单独的@Tests中,调用UiTest.start(),无论运行多少测试,它只会一次启动javafx。
我的实际经验:JavaFx组件<->属性关系“有效”。也就是说,由于它不是我的代码,因此我从测试中收获很少,并且它“每次都有效”。属性之间的关系是我的代码是而不是 总是起作用的。这就是为什么我使用ViewModel,在其中放置属性之间的交互,并使用microtests对其进行严格测试的原因,这不需要运行JavaFx线程。 (JavaFx属性在其回调中不使用多线程,因此addListener目标被同步调用。)当您需要巧妙使用绑定时,这特别方便。在TornadoFx视图构建器中内联复杂的JavaFx绑定是一个杯子的游戏。将它们提取到模型中很容易。
我通过反复试验学到了这一切。在堆栈溢出中有描述的第三种方法可以使之起作用。这等于使您的测试的100%在JavaFx线程中运行。我发现这很困难,因为我的大部分工作都不在生产中的线程上。。 Basic JUnit test for JavaFX 8
祝您好运,如果您需要更多帮助,请与我们联系! 吉宝山
P.S。向埃德文大喊:TornadoFx的出色工作。我每天都用它。
答案 1 :(得分:1)
您需要初始化JavaFX Toolkit。如果您使用的是TestFX,则可以调用.each
,否则,可以实例化FxToolkit.registerPrimaryStage()
以实现相同的目标。