2015-01-30 15 views
16

[lời mở đầu: xin lỗi, có rất nhiều mã ở đây và một số mã có thể không liên quan đến câu hỏi này trong khi một số mã cần thiết để hiểu vấn đề có thể bị thiếu; vui lòng nhận xét và tôi sẽ chỉnh sửa câu hỏi tương ứng.]Làm thế nào để bạn giả lập một bộ công cụ JavaFX khởi tạo?

Môi trường: Ubuntu 14,10 x86_64; Oracle JDK 1.8u25. Thư viện kiểm thử đơn vị là TestNG, phiên bản 6.8.13; Mockito là phiên bản 1.10.17. Trong ứng dụng GUI của tôi, cái mà JavaFX gọi là "điều khiển" khá thụ động, theo nghĩa là điều duy nhất mà "bộ điều khiển" này (mà tôi gọi là "màn hình") thực sự là gửi các sự kiện.

Bây giờ, khi một sự kiện được nhận yêu cầu cập nhật GUI, nó là một lớp khác, mà tôi gọi là chế độ xem, chịu trách nhiệm cập nhật GUI. Nói tóm lại:

hiển thị -> người dẫn chương trình -> view -> hiển thị

tôi có các unit test cho hai trong số các:

  • hiển thị -> dẫn chương trình;
  • người trình bày -> chế độ xem.

Vì vậy, tôi được đề cập nhiều về mặt trước này (với lợi thế là tôi có thể thay đổi hiển thị, đó là lý do tôi làm theo cách đó).

Nhưng bây giờ tôi thử và kiểm tra phần "xem -> hiển thị"; và tôi là SOL.

Là một minh hoạ, đây là lớp xem:

@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); 
    } 
} 

Lớp hiển thị phù hợp là thế này:

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")); 
     } 
    } 
} 

và đây là tập tin thử nghiệm của tôi:

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); 
    } 
} 

tôi dự kiến ​​nó sẽ hoạt động ... Ngoại trừ việc nó không. Tuy nhiên "xa nhau" Tôi cố gắng tránh xa từ các mã nền tảng, ngay cả những lớp thử nghiệm trên không thành công với ngoại lệ này:

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 

Vì vậy, trong ngắn hạn, làm thế nào để ngăn chặn sự ngoại lệ trên xảy ra? Tôi đã nghĩ rằng mocking các vật dụng đi là đủ, nhưng dường như không:/Có vẻ như tôi cần phải thử toàn bộ bối cảnh "nền tảng" (vì thiếu một từ tốt hơn cho nó) nhưng tôi không có ý tưởng làm thế nào.

+1

Tôi không có kinh nghiệm với các công cụ cụ thể trong chuỗi công cụ của mình, vì vậy tôi không thể tư vấn cho bạn ở đó, nhưng bạn có thể áp dụng điều gì đó từ chiến lược được sử dụng cho [khởi tạo bộ công cụ JavaFX để kiểm tra đơn vị môi trường JUnit] (https://gist.github.com/andytill/3835914). – jewelsea

+0

@jewelsea thú vị! Tôi sẽ cố gắng và điều chỉnh điều này để TestNG – fge

+0

@jewelsea thích ứng thất bại, không may:/Tôi không thể làm cho nó hoạt động ... – fge

Trả lời

10

Ok, những điều đầu tiên đầu tiên: Tôi không bao giờ sử dụng Mockito một lần trong đời. Nhưng tôi đã tò mò, vì vậy tôi đã dành nhiều giờ để tìm ra điều này và tôi đoán có nhiều thứ để cải thiện.

Vì vậy, để làm việc này, chúng ta cần:

  1. The aforementioned (by @jewelsea) JUnit Threading Rule.
  2. Một thực hiện tùy chỉnh MockMaker, gói mặc định CglibMockMaker.
  3. Kết nối mọi thứ với nhau.

Vì vậy, 1 + 2 là thế này:

public class JavaFXMockMaker implements MockMaker { 

    private final MockMaker wrapped = new CglibMockMaker(); 
    private boolean jfxIsSetup; 

    private void doOnJavaFXThread(Runnable pRun) throws RuntimeException { 
     if (!jfxIsSetup) { 
      setupJavaFX(); 
      jfxIsSetup = true; 
     } 
     final CountDownLatch countDownLatch = new CountDownLatch(1); 
     Platform.runLater(() -> { 
      pRun.run(); 
      countDownLatch.countDown(); 
     }); 

     try { 
      countDownLatch.await(); 
     } catch (InterruptedException e) { 
      throw new RuntimeException(e); 
     } 
    } 

    protected void setupJavaFX() throws RuntimeException { 
     final CountDownLatch latch = new CountDownLatch(1); 
     SwingUtilities.invokeLater(() -> { 
      new JFXPanel(); // initializes JavaFX environment 
      latch.countDown(); 
     }); 

     try { 
      latch.await(); 
     } catch (InterruptedException e) { 
      throw new RuntimeException(e); 
     } 
    } 

    @Override 
    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) { 
     AtomicReference<T> result = new AtomicReference<>(); 
     Runnable run =() -> result.set(wrapped.createMock(settings, handler)); 
     doOnJavaFXThread(run); 
     return result.get(); 
    } 

    @Override 
    public MockHandler getHandler(Object mock) { 
     AtomicReference<MockHandler> result = new AtomicReference<>(); 
     Runnable run =() -> result.set(wrapped.getHandler(mock)); 
     doOnJavaFXThread(run); 
     return result.get(); 
    } 

    @Override 
    public void resetMock(Object mock, MockHandler newHandler, @SuppressWarnings("rawtypes") MockCreationSettings settings) { 
     Runnable run =() -> wrapped.resetMock(mock, newHandler, settings); 
     doOnJavaFXThread(run); 
    } 

} 

Số 3 chỉ đang theo hướng dẫn:

  1. Sao chép tên lớp đầy đủ của MockMaker của chúng tôi, ví dụ. org.awesome.mockito.JavaFXMockMaker.
  2. Tạo tệp "mockito-extensions/org.mockito.plugins.MockMaker". Nội dung của tệp này chính xác là một dòng có tên đủ điều kiện.

Thử nghiệm hạnh phúc & kudo to Andy Till cho quy tắc luồng của mình.


Cảnh báo: Đây loại thực hiện cứng mã các MockMaker sử dụng CglibMockMaker mà có thể không được là những gì bạn muốn trong mọi trường hợp (xem JavaDocs).

+0

Như đã thảo luận trong phòng: một thử nghiệm đơn giản làm việc tốt; lượng nghiên cứu ấn tượng! Tôi sẽ thử một vài bài kiểm tra nữa, nhưng theo như tôi lo ngại đó là một khoản tiền xứng đáng +525 cho bạn – fge

+0

Vẫn còn 5 giờ trước khi tôi có thể trao tiền thưởng nhưng đó là của bạn, chắc chắn! Tôi điên cuồng viết mã thử nghiệm cho tất cả 'JavafxView' và tất cả đều hoạt động! – fge

+0

Vui mừng khi biết rằng nó hoạt động :-) – eckig

Các vấn đề liên quan