EclEmma "Coverage As" Eclipse JUnit 线程死亡

EclEmma "Coverage As" Eclipse JUnit Thread Death

我使用 TestFX 编写了以下 JUnit4 测试来测试特定产品(待办事项列表)的 GUI (JavaFX),以及两个必要的 classes。第一个 class 是管理整个 GUI 的主要 class,而第二个是用于文本字段的 class。如有必要,完整的源代码位于 here(它是已经提交的学校项目的一部分)。

如果我只是 运行 使用 Eclipse 中的 F11 热键或 "Run As -> JUnit Test",测试将完全正常。但是,当我 select "Coverage" 时,它会在第一个测试用例上出现错误(无论我选择将哪个设置为第一个)。具体来说,它 "types" 第一个测试用例的前两个字符(这里的示例用例中为 sh),然后给我检测到用户输入的错误 ([TestFX] User mouse movement detected. Aborting test.) 然后它移至下一个测试用例。

我一直无法自己弄清楚,而且我似乎无法在网上找到太多关于这方面的帮助。任何帮助将不胜感激!根据堆栈跟踪,它看起来与线程有关,但我看不出覆盖率 运行ning 将如何导致这种情况(当正常测试没有时)。

我不得不缩短堆栈跟踪,因为我达到了极限。

java.lang.RuntimeException: java.lang.ThreadDeath
at org.loadui.testfx.utils.FXTestUtils.awaitEvents(FXTestUtils.java:104)
at org.loadui.testfx.FXScreenController.release(FXScreenController.java:131)
at org.loadui.testfx.GuiTest.release(GuiTest.java:1110)
at org.loadui.testfx.GuiTest.type(GuiTest.java:1069)
at org.loadui.testfx.GuiTest.type(GuiTest.java:1008)
at org.loadui.testfx.GuiTest.type(GuiTest.java:990)
at gui.UserInterfaceTest.test1ShowUndoneEmpty(UserInterfaceTest.java:38)
Caused by: java.lang.ThreadDeath
at java.lang.Thread.stop(Unknown Source)
at org.loadui.testfx.utils.UserInputDetector.userInputDetected(UserInputDetector.java:58)
at org.loadui.testfx.utils.UserInputDetector.assertPointsAreEqual(UserInputDetector.java:42)
at org.loadui.testfx.utils.UserInputDetector.run(UserInputDetector.java:27)
at java.lang.Thread.run(Unknown Source)

UserInterface.java

package gui;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.scene.Scene;
import object.Task;
import type.CommandType;
import type.KeywordType;
import logic.FeedbackHelper;
import logic.LogicController;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

//@@author A0112882H
public class UserInterface extends Application {
    private static final int ROW_HEIGHT = 30;

    private static BorderPane _root = new BorderPane();
    private static Scene _defaultScene = new Scene(_root, 750, 580);
    private static VBox _vbox = new VBox();
    private static VBox _tables = new VBox();

    private static UIButton _taskButton = new UIButton("Tasks & Events");
    private static UIButton _floatingButton = new UIButton("Floating Tasks");
    private static UITextField _field = new UITextField();

    private static TextArea _cheatSheet = new TextArea();
    private static Label _feedBack = new Label();
    private static int commandIndex;

    private static UITable _taskTable = new UITable(false);
    private static UITable _floatingTable = new UITable(true);

    private final KeyCombination _undoKey = new KeyCodeCombination(KeyCode.U, KeyCombination.CONTROL_DOWN);
    private final KeyCombination _redoKey = new KeyCodeCombination(KeyCode.R, KeyCombination.CONTROL_DOWN);
    private final KeyCombination _homeKey = new KeyCodeCombination(KeyCode.H, KeyCombination.CONTROL_DOWN);

    private static ArrayList<String> commandHistory = new ArrayList<String>();
    private static ArrayList<Task> _displayList = new ArrayList<Task>();

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        _root.setOnKeyPressed(hotKeyEvents);
        _field.setOnKeyPressed(hotKeyEvents);

        setScene();
        setUpCommandPrompt();
        setUpTables();

        setKeywordsHighlighting();

        primaryStage.setScene(_defaultScene);
        primaryStage.setTitle("F2DO");
        primaryStage.show();
    }

    public BorderPane getRootNode() {
        return _root;
    }

    private void setScene() {
        String css = UserInterface.class.getResource("style.css").toExternalForm();
        _defaultScene.getStylesheets().add(css);

        _defaultScene.heightProperty().addListener(new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                int displaySize = (int) Math.floor(_taskTable.getHeight() / ROW_HEIGHT) - 1;
                LogicController.setNonFloatingDisplaySize(displaySize);
                updateDisplayList();
            }

        });
    }

    /**
     * Set the hot keys. Ctrl + U: undo operation. Ctrl + R: redo operation.
     * Ctrl + H: home page. F1: help page. F2: show all. F3: show undone tasks.
     * F4: show done tasks. ESC: exit application.
     */
    private EventHandler<KeyEvent> hotKeyEvents = new EventHandler<KeyEvent>() {

        @Override
        public void handle(KeyEvent event) {

            String showUndone = "show undone";
            String showDone = "show done";
            String showAll = "show all";

            if (_undoKey.match(event)) {
                String feedbackMsg = LogicController.undo();
                _feedBack.setText(feedbackMsg);
                updateDisplayList();
            } else if (_redoKey.match(event)) {
                String feedbackMsg = LogicController.redo();
                _feedBack.setText(feedbackMsg);
                updateDisplayList();
            } else if (_homeKey.match(event)) {
                initialiseScene();
                setUpCommandPrompt();
                setUpTables();
            } else if (event.getCode().equals(KeyCode.F3)) {
                String feedbackMsg = LogicController.process(showUndone, _displayList);
                _feedBack.setText(feedbackMsg);
                updateDisplayList();
            } else if (event.getCode().equals(KeyCode.F4)) {
                String feedbackMsg = LogicController.process(showDone, _displayList);
                _feedBack.setText(feedbackMsg);
                updateDisplayList();
            } else if (event.getCode().equals(KeyCode.F2)) {
                String feedbackMsg = LogicController.process(showAll, _displayList);
                _feedBack.setText(feedbackMsg);
                updateDisplayList();
            } else if (event.getCode().equals(KeyCode.F1)) {
                try {
                    initialiseScene();
                    setUpCommandPrompt();
                    setCheatSheetContent();
                } catch (Exception e) {
                }
            } else if (event.getCode().equals(KeyCode.ESCAPE)) {
                exit();
            } else if (event.getCode().equals(KeyCode.ENTER)) {
                String userInput = _field.getText();
                commandHistory.add(userInput);
                commandIndex = commandHistory.size() - 1;

                _field.clear();
                event.consume();

                String feedbackMsg = LogicController.process(userInput, _displayList);

                if (feedbackMsg == FeedbackHelper.MSG_HELP) {
                    try {
                        initialiseScene();
                        setUpCommandPrompt();
                        setCheatSheetContent();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else if (feedbackMsg == FeedbackHelper.MSG_HOME) {
                    initialiseScene();
                    setUpCommandPrompt();
                    setUpTables();
                } else {
                    _feedBack.setText(feedbackMsg);
                    updateDisplayList();
                }
            } else if (event.getCode().equals(KeyCode.UP)) {

                if (!commandHistory.isEmpty()) {
                    _field.replaceText(commandHistory.get(commandIndex));
                    int length = commandHistory.get(commandIndex).length();
                    commandIndex--;

                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {
                            _field.positionCaret(length);
                        }
                    });

                    if (commandIndex < 0) {
                        commandIndex = 0;
                    }
                }
            } else if (event.getCode().equals(KeyCode.DOWN)) {
                _field.showPopup();
            }

        }

    };

    /**
     * Set up command prompt and feedback
     */
    private void setUpCommandPrompt() {
        setTextArea();
        setFeedback();

        _field.setId("textarea");
        _feedBack.setId("feedback");

        _vbox.setAlignment(Pos.CENTER);
        _vbox.setSpacing(5);
        _vbox.getChildren().addAll(_field, _feedBack);
        BorderPane.setMargin(_vbox, new Insets(20, 20, 0, 20));

        _root.setTop(_vbox);
    }

    /**
     * Set up labels and tables
     */
    private void setUpTables() {
        updateDisplayList();

        BorderPane.setMargin(_tables, new Insets(8, 20, 30, 20));
        BorderPane.setAlignment(_tables, Pos.CENTER);

        _floatingTable.setId("floatingTable");
        _taskTable.setId("taskTable");

        _taskButton.setMaxWidth(Double.MAX_VALUE);
        _floatingButton.setMaxWidth(Double.MAX_VALUE);
        _taskButton.setStyle("-fx-font-size: 13.5; -fx-font-weight: bold");
        _floatingButton.setStyle("-fx-font-size: 13.5; -fx-font-weight: bold");

        _tables.setAlignment(Pos.CENTER);
        _tables.getChildren().addAll(_taskButton, _taskTable, _floatingButton, _floatingTable);
        _tables.setSpacing(7);

        _root.setCenter(_tables);
    }

    /**
     * Update tables.
     */
    private static void updateDisplayList() {
        ArrayList<Task> nonFloatingList = LogicController.getNonFloatingList();
        ArrayList<Task> floatingList = LogicController.getFloatingList();

        _displayList.clear();
        _displayList.addAll(nonFloatingList);
        _displayList.addAll(floatingList);

        _taskTable.updateTable(nonFloatingList, floatingList);
        _floatingTable.updateTable(nonFloatingList, floatingList);
        _field.updateDisplayList(_displayList);
    }

    /**
     * Set the design of textArea
     */
    private void setTextArea() {
        _field.setPrefHeight(25);
        _field.setMaxHeight(25);
        _field.setPadding(new Insets(2, 2, 2, 2));
        _field.setWrapText(true);
        _field.setStyle("-fx-border-color: lightblue; -fx-font-size: 14");
    }

    /**
     * Set the design of feedback.
     * 
     * @param feedback
     */
    private void setFeedback() {
        _feedBack.setText("Welcome to F2DO, your personalised task manager(:\n" + "Type " + "\"Help\""
                + " for a list of commands to get started.");
        _feedBack.setMouseTransparent(true);
    }

    /**
     * Set highlighting of the keyword.
     */
    private void setKeywordsHighlighting() {

        _field.textProperty().addListener((observable, oldValue, newValue) -> {
            // check if the first word is a keyword - happens in most cases
            // for commands e.g. like add, search, edit, delete
            String firstWord = getFirstWord(newValue);

            if (isValidCmd(firstWord)) {
                _field.setStyle(0, firstWord.length(), "-fx-font-weight: bold; -fx-fill: red");
                if (newValue.length() > firstWord.length()) {
                    _field.setStyle(firstWord.length() + 1, newValue.length(),
                            "-fx-font-weight: normal; -fx-fill: black");
                }

                String[] result = newValue.substring(firstWord.length()).split("\s");
                int currentIndex = firstWord.length();
                for (int i = 0; i < result.length; i++) {
                    String word = result[i];
                    if (isValidKeyword(word)) {
                        _field.setStyle(currentIndex, currentIndex + word.length(),
                                "-fx-font-weight: bold; -fx-fill: blue");
                    }
                    currentIndex += word.length() + 1;
                }

            } else {
                _field.setStyle(0, newValue.length(), "-fx-font-weight: normal; -fx-fill: black");
            }
        });
    }

    /**
     * Get the first word of the command.
     * 
     * @param newCommand
     *            - input command
     * @return first word
     */
    private String getFirstWord(String newCommand) {

        String[] textTokens = newCommand.split(" ");

        if (textTokens.length > 0) {
            return textTokens[0];
        }

        return null;
    }

    /**
     * Check if the entered word is a valid command.
     * 
     * @param word
     *            - input word
     * @return true if the word is a valid command; false otherwise
     */
    private boolean isValidCmd(String word) {

        if (CommandType.toCmd(word) != CommandType.INVALID) {
            return true;
        }
        return false;
    }

    /**
     * Check if the entered word is a valid keyword.
     * 
     * @param word
     *            - input word
     * @return true if the word is a valid keyword; false otherwise
     */
    private boolean isValidKeyword(String word) {

        if (KeywordType.toType(word) != KeywordType.INVALID) {
            return true;
        }
        return false;
    }

    private void initialiseScene() {
        _vbox.getChildren().clear();
        _tables.getChildren().clear();
        _root.getChildren().clear();
    }

    private void setCheatSheetContent() throws IOException {
        String text;
        StringBuilder content = new StringBuilder();

        _cheatSheet.setEditable(false);

        BorderPane.setMargin(_cheatSheet, new Insets(8, 20, 25, 20));

        InputStream is = getClass().getResourceAsStream("cheatsheet.txt");
        BufferedReader br = new BufferedReader(new InputStreamReader(is));

        while ((text = br.readLine()) != null) {
            content.append(text).append("\n");
        }

        _cheatSheet.clear();
        _cheatSheet.appendText(content.toString());

        _root.setCenter(_cheatSheet);

        br.close();
    }

    private void exit() {
        Platform.exit();
    }
}

UITextField.java

package gui;

import org.fxmisc.richtext.InlineCssTextArea;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Side;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import object.Task;
import type.CommandType;
import type.TaskType;

//@@author A0118005W
public class UITextField extends InlineCssTextArea {
    private static ArrayList<Task> _displayList = new ArrayList<Task>();
    private ContextMenu popupMenu = new ContextMenu();

    public UITextField() {
        super();
        setAutoFill();
    }

    /**
     * Update the display list in TextField.
     * 
     * @param displayList
     *            - display list
     */
    public void updateDisplayList(ArrayList<Task> displayList) {
        _displayList = displayList;
    }

    /**
     * Show pop-up menu.
     */
    public void showPopup() {
        if (!popupMenu.isShowing()) {
            popupMenu.show(this, Side.BOTTOM, 0, 0);
        }
    }

    /**
     * Set up auto fill in of the text field.
     */
    private void setAutoFill() {
        this.textProperty().addListener(new ChangeListener<String>() {

            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                String text = UITextField.this.getText();
                String[] textTokens = text.split(" ");

                popupMenu.hide();

                int spaceCount = 0;
                for (int i = 0; i < text.length() && spaceCount < 2; i++) {
                    if (text.charAt(i) == ' ') {
                        spaceCount += 1;
                    }
                }

                if (textTokens.length == 2 && spaceCount == 2) {
                    String firstToken = textTokens[0];
                    CommandType cmd = CommandType.toCmd(firstToken);
                    int index = getInteger(textTokens[1]) - 1;
                    boolean isWithinRange = ((index >= 0) && (index < _displayList.size()));

                    if (cmd == CommandType.EDIT && isWithinRange) {
                        Task task = _displayList.get(index);
                        populatePopup(index, task);

                        if (!popupMenu.isShowing()) {
                            popupMenu.show(UITextField.this, Side.BOTTOM, 0, 0);
                        }
                    }
                } else if (textTokens.length <= 2) {
                    // Hide pop up
                    popupMenu.hide();
                    popupMenu.getItems().clear();
                }

            }

        });
    }

    /**
     * Get the integer from an input string. If the input cannot be parsed,
     * return -1.
     * 
     * @param input
     *            - input string
     * @return parsed integer
     */
    private int getInteger(String input) {
        try {
            int integer = Integer.parseInt(input);
            return integer;
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    /**
     * Populate the pop-up box.
     * 
     * @param index
     *            - index of the task
     * @param task
     *            - task to be displayed
     */
    private void populatePopup(int index, Task task) {
        ArrayList<String> displayList = getDisplayItems(index, task);
        ArrayList<CustomMenuItem> menuItems = new ArrayList<CustomMenuItem>();

        for (int i = 0; i < displayList.size(); i++) {
            String str = displayList.get(i);
            Label label = new Label(str);
            CustomMenuItem item = new CustomMenuItem(label, true);

            item.setOnAction(new EventHandler<ActionEvent>() {

                @Override
                public void handle(ActionEvent event) {
                    replaceText(str);
                    positionCaret(str.length());
                }

            });

            menuItems.add(item);
        }

        popupMenu.getItems().clear();
        popupMenu.getItems().addAll(menuItems);
    }

    /**
     * Get the command input to be displayed in the pop-up menu.
     * 
     * @param index
     *            - index of the task
     * @param task
     *            - task to be displayed
     * @return display items
     */
    private ArrayList<String> getDisplayItems(int index, Task task) {
        ArrayList<String> items = new ArrayList<String>();
        TaskType taskType = task.getTaskType();

        Integer displayIndex = index + 1;
        String floatingStr = "edit " + displayIndex.toString() + " " + task.getTaskName() + " ";
        String eventStr = floatingStr;
        String alternateEventStr = floatingStr;
        String deadlineStr = floatingStr;

        Calendar tmrCalendar = Calendar.getInstance();
        Calendar afterTmrCalendar = Calendar.getInstance();
        tmrCalendar.add(Calendar.DAY_OF_MONTH, 1);
        afterTmrCalendar.add(Calendar.DAY_OF_MONTH, 2);

        Date tomorrow = tmrCalendar.getTime();
        Date afterTomorrow = afterTmrCalendar.getTime();
        Date startDate = task.getStartDate();
        Date endDate = task.getEndDate();

        SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm");

        // Set event string
        if (startDate != null && endDate != null) {
            eventStr += "from " + dateFormat.format(startDate) + " ";
            eventStr += "to " + dateFormat.format(endDate);

            alternateEventStr += "on " + dateFormat.format(startDate);
        } else if (startDate != null) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(startDate);
            calendar.add(Calendar.DAY_OF_MONTH, 1);

            eventStr += "on " + dateFormat.format(startDate);

            alternateEventStr += "from " + dateFormat.format(startDate) + " ";
            alternateEventStr += "to " + dateFormat.format(calendar.getTime());
        } else if (endDate != null) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(endDate);
            calendar.add(Calendar.DAY_OF_MONTH, 1);

            eventStr += "from " + dateFormat.format(endDate) + " ";
            eventStr += "to " + dateFormat.format(calendar.getTime());

            alternateEventStr += "on " + dateFormat.format(endDate);

        } else {
            eventStr += "from " + dateFormat.format(tomorrow) + " ";
            eventStr += "to " + dateFormat.format(afterTomorrow);

            alternateEventStr += "on " + dateFormat.format(tomorrow);
        }

        // Set deadline string
        if (endDate != null) {
            deadlineStr += "by " + dateFormat.format(endDate);
        } else if (startDate != null) {
            deadlineStr += "by " + dateFormat.format(startDate);
        } else {
            deadlineStr += "by " + dateFormat.format(tomorrow);
        }

        // Assign display order
        int eventIndex = 0;
        int floatingIndex = 1;
        int alternateEventIndex = 2;
        int deadlineIndex = 3;
        int firstIndex = -1;

        String[] eventList = { eventStr, floatingStr, alternateEventStr, deadlineStr };

        switch (taskType) {
        case EVENT:
            if (endDate == null) {
                items.add(eventList[alternateEventIndex]);
                firstIndex = alternateEventIndex;
            } else {
                items.add(eventList[eventIndex]);
                firstIndex = eventIndex;
            }
            break;
        case DEADLINE:
            items.add(eventList[deadlineIndex]);
            firstIndex = deadlineIndex;
            break;
        case FLOATING:
            items.add(eventList[floatingIndex]);
            firstIndex = floatingIndex;
            break;
        default:
            // Do nothing
        }

        for (int i = 0; i < eventList.length; i++) {
            if (i != firstIndex) {
                items.add(eventList[i]);
            }
        }

        return items;
    }
}

UserInterfaceTest.java

package gui;

import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import org.junit.Test;
import org.loadui.testfx.Assertions;
import org.loadui.testfx.GuiTest;
import org.loadui.testfx.utils.FXTestUtils;

import javafx.scene.Parent;
import javafx.scene.input.KeyCode;

//@@author A0112882H-reused
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UserInterfaceTest {
    private static GuiTest controller;

    @BeforeClass
    public static void setUpClass() throws InterruptedException {
        FXTestUtils.launchApp(UserInterface.class);
        Thread.sleep(7000); // Giving the program time to startup. The likely problematic line.

        controller = new GuiTest() {
            @Override
            protected Parent getRootNode() {
                return stage.getScene().getRoot();
            }
        };
        System.out.println("GUI TEST START");

    }

    // @@author A0112882H
    @Test
    public void test1ShowUndoneEmpty() throws Exception {
        UITextField textField = (UITextField) GuiTest.find("#textarea");
        controller.click(textField).type("show undone").push(KeyCode.ENTER);
        // Assertions.assertNodeExists("");
    }

    @Test
    public void test2AddFloatingTask() throws Exception {
        UITextField textField = (UITextField) GuiTest.find("#textarea");
        controller.click(textField).type("add Meeting with boss").push(KeyCode.ENTER);
        Assertions.assertNodeExists("Meeting with boss");
    }

    @Test
    public void test3Search() throws Exception {
        UITextField textField = (UITextField) GuiTest.find("#textarea");
        controller.click(textField).type("search Meeting with boss").push(KeyCode.ENTER);
        Assertions.assertNodeExists("Meeting with boss");
    }

    @Test
    public void test4ShowUndone() throws Exception {
        UITextField textField = (UITextField) GuiTest.find("#textarea");
        controller.click(textField).type("show undone").push(KeyCode.ENTER);
        Assertions.assertNodeExists("Meeting with boss");
    }

    @Test
    public void test5MarkDone() throws Exception {
        UITextField textField = (UITextField) GuiTest.find("#textarea");
        controller.click(textField).type("done 1").push(KeyCode.ENTER);
        // Assertions.assertNodeExists("Meeting with boss");
    }
}

P.S。对不起,如果我添加了不必要的标签。不确定我应该包括什么。

P.P.S。我从来没有让测试文件中的断言完全起作用。如果你愿意,你可以忽略它们,因为我不想学习如何解决这个问题(现在)。

您的线程被外部源中断(停止),异常被抛出,同时也中断了测试。抓住它,忽略它。

try {
    Thread.sleep(7000);
} 
catch (InterruptedException e) {}

一个好的测试应该是快速的,并且不依赖于外部事件或 类(您正在测试的除外)。也许你应该尝试做更小、更有针对性的测试。

由于 GUI 在其自己的线程内工作,因此您自己的 JUnit 测试结束会终止它。确保正确关闭 GUI。