[preambolo: ci scusiamo, qui c'è un sacco di codice, e alcuni potrebbero non essere pertinenti a questa domanda, mentre potrebbe mancare del codice necessario per capire il problema; per favore commenta e modifico la domanda di conseguenza.]Come si prende in giro l'inizializzazione del toolkit JavaFX?
Ambiente: Ubuntu 14.10 x86_64; Oracle JDK 1.8u25. La libreria di test delle unità è TestNG, versione 6.8.13; Mockito è la versione 1.10.17.
Nella mia applicazione GUI, quello che JavaFX chiama un "controller" è piuttosto passivo, nel senso che l'unica cosa che questo "controller" (che io chiamo "display") fa davvero è inviare eventi.
Ora, quando viene ricevuto un evento che richiede un aggiornamento della GUI, è un'altra classe, che io chiamo una vista, che è responsabile dell'aggiornamento della GUI. In breve:
visualizzazione -> presenter -> vista -> visualizzazione
ho test di unità per due di questi:
- visualizzazione -> presentatore; presentatore
- -> visualizzazione.
Quindi, sono praticamente coperto da questo fronte (con il vantaggio di poter cambiare il display, ecco perché lo sto facendo in questo modo).
Ma ora provo a testare la parte "visualizza -> visualizza"; e io sono SOL.
A titolo di esempio, ecco la classe di visualizzazione:
@NonFinalForTesting
public class JavafxTreeTabView
extends JavafxView<TreeTabPresenter, TreeTabDisplay>
implements TreeTabView
{
private final BackgroundTaskRunner taskRunner;
public JavafxTreeTabView(final BackgroundTaskRunner taskRunner)
throws IOException
{
super("/tabs/treeTab.fxml");
this.taskRunner = taskRunner;
}
JavafxTreeTabView(final BackgroundTaskRunner taskRunner,
final Node node, final TreeTabDisplay display)
{
super(node, display);
this.taskRunner = taskRunner;
}
@Override
public void loadTree(final ParseNode rootNode)
{
taskRunner.compute(() -> buildTree(rootNode), value -> {
display.parseTree.setRoot(value);
display.treeExpand.setDisable(false);
});
}
@Override
public void loadText(final InputBuffer buffer)
{
final String text = buffer.extract(0, buffer.length());
display.inputText.getChildren().setAll(new Text(text));
}
@VisibleForTesting
TreeItem<ParseNode> buildTree(final ParseNode root)
{
return buildTree(root, false);
}
private TreeItem<ParseNode> buildTree(final ParseNode root,
final boolean expanded)
{
final TreeItem<ParseNode> ret = new TreeItem<>(root);
addChildren(ret, root, expanded);
return ret;
}
private void addChildren(final TreeItem<ParseNode> item,
final ParseNode parent, final boolean expanded)
{
TreeItem<ParseNode> childItem;
final List<TreeItem<ParseNode>> childrenItems
= FXCollections.observableArrayList();
for (final ParseNode node: parent.getChildren()) {
childItem = new TreeItem<>(node);
addChildren(childItem, node, expanded);
childrenItems.add(childItem);
}
item.getChildren().setAll(childrenItems);
item.setExpanded(expanded);
}
}
La classe di visualizzazione di corrispondenza è questo:
public class TreeTabDisplay
extends JavafxDisplay<TreeTabPresenter>
{
@FXML
protected Button treeExpand;
@FXML
protected TreeView<ParseNode> parseTree;
@FXML
protected TextFlow inputText;
@Override
public void init()
{
parseTree.setCellFactory(param -> new ParseNodeCell(presenter));
}
@FXML
void expandParseTreeEvent(final Event event)
{
}
private static final class ParseNodeCell
extends TreeCell<ParseNode>
{
private ParseNodeCell(final TreeTabPresenter presenter)
{
setEditable(false);
selectedProperty().addListener(new ChangeListener<Boolean>()
{
@Override
public void changed(
final ObservableValue<? extends Boolean> observable,
final Boolean oldValue, final Boolean newValue)
{
if (!newValue)
return;
final ParseNode node = getItem();
if (node != null)
presenter.parseNodeShowEvent(node);
}
});
}
@Override
protected void updateItem(final ParseNode item, final boolean empty)
{
super.updateItem(item, empty);
setText(empty ? null : String.format("%s (%s)", item.getRuleName(),
item.isSuccess() ? "SUCCESS" : "FAILURE"));
}
}
}
Ed ecco il mio file di test:
public final class JavafxTreeTabViewTest
{
private final Node node = mock(Node.class);
private final BackgroundTaskRunner taskRunner = new BackgroundTaskRunner(
MoreExecutors.newDirectExecutorService(), Runnable::run
);
private JavafxTreeTabView view;
private TreeTabDisplay display;
@BeforeMethod
public void init()
throws IOException
{
display = new TreeTabDisplay();
view = spy(new JavafxTreeTabView(taskRunner, node, display));
}
@Test
public void loadTreeTest()
{
final ParseNode rootNode = mock(ParseNode.class);
final TreeItem<ParseNode> item = mock(TreeItem.class);
doReturn(item).when(view).buildTree(same(rootNode));
display.parseTree = mock(TreeView.class);
display.treeExpand = mock(Button.class);
view.loadTree(rootNode);
verify(display.parseTree).setRoot(same(item));
verify(display.treeExpand).setDisable(false);
}
}
I mi aspettavo che funzionasse ... Tranne che no. Tuttavia "lontani" Cerco di stare lontani dal codice della piattaforma, anche la classe di test precedente non riesce con questa eccezione:
java.lang.ExceptionInInitializerError
at sun.reflect.GeneratedSerializationConstructorAccessor5.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:45)
at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:73)
at org.mockito.internal.creation.instance.ObjenesisInstantiator.newInstance(ObjenesisInstantiator.java:14)
at org.mockito.internal.creation.cglib.ClassImposterizer.createProxy(ClassImposterizer.java:143)
at org.mockito.internal.creation.cglib.ClassImposterizer.imposterise(ClassImposterizer.java:58)
at org.mockito.internal.creation.cglib.ClassImposterizer.imposterise(ClassImposterizer.java:49)
at org.mockito.internal.creation.cglib.CglibMockMaker.createMock(CglibMockMaker.java:24)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:33)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:59)
at org.mockito.Mockito.mock(Mockito.java:1285)
at org.mockito.Mockito.mock(Mockito.java:1163)
at com.github.fge.grappa.debugger.csvtrace.tabs.JavafxTreeTabViewTest.loadTreeTest(JavafxTreeTabViewTest.java:46)
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:483)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
at org.testng.TestRunner.privateRun(TestRunner.java:767)
at org.testng.TestRunner.run(TestRunner.java:617)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:348)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:343)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:305)
at org.testng.SuiteRunner.run(SuiteRunner.java:254)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
at org.testng.TestNG.run(TestNG.java:1057)
at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:125)
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:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:270)
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:265)
at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:540)
at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:502)
at javafx.scene.control.Control.<clinit>(Control.java:87)
... 44 more
Così, in breve, Come posso evitare l'eccezione di cui sopra accada? Avrei pensato che prendere in giro i widget sarebbe stato sufficiente, ma apparentemente no:/Sembra che abbia bisogno di prendere in giro l'intero "contesto di piattaforma" (per mancanza di una parola migliore) ma non ho idea di come.
Non ho esperienza con gli strumenti specifici nella tua toolchain, quindi non posso consigliarti lì, ma potresti essere in grado di adottare qualcosa da una strategia che è stata usata per [inizializzare il toolkit JavaFX per i test unitari in un ambiente JUnit] (https://gist.github.com/andytill/3835914). – jewelsea
@jewelsea interessante! Cercherò di adattare questo a TestNG – fge
@jewelsea adattamento fallito, sfortunatamente:/Non riesco a farlo funzionare ... – fge