0

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

如果我只是在 Eclipse 中使用 F11 热键或“运行方式 -> JUnit 测试”来运行它,那么该测试工作得非常好。但是,当我选择“Coverage”时,它会在第一个测试用例上出错(无论我选择设置为第一个)。具体来说,它“键入”第一个测试用例的前两个字符(此处的示例用例中的 sh),然后给我检测到用户输入的错误([TestFX] User mouse movement detected. Aborting test.)然后转到下一个测试用例。

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

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

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)

用户界面.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;
    }
}

用户界面测试.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");
    }
}

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

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

4

1 回答 1

0

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

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

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

由于 GUI 在其自己的线程中工作,您自己的 JUnit 测试结束将杀死它。确保正确关闭 GUI。

于 2015-11-27T01:03:46.153 回答