特定の製品 (to-do リスト) の GUI (JavaFX) をテストするために、TestFX を使用して以下の JUnit4 テストを作成し、必要な 2 つのクラスを作成しました。最初のクラスは GUI 全体を管理するメイン クラスで、2 番目はテキスト フィールドのクラスです。完全なソース コードは、必要に応じてここにあります(すでに提出されている学校のプロジェクトの一部でした)。
Eclipse で F11 ホットキー、または「Run As -> JUnit Test」を使用してテストを実行すると、テストは完全に正常に機能します。ただし、「カバレッジ」を選択すると、最初のテストケースにバグが発生します(最初に設定したものに関係なく)。具体的には、最初のテスト ケースの最初の 2 文字 (ここのサンプル ケースでは 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)
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");
}
}
PS不要なタグを付けてすみません。何を含めるべきだったのかわからない。
PPSテストファイルのアサーションが完全に機能することはありませんでした。私はそれを解決する方法を知りたいとは思っていないので (今のところ)、必要に応じて無視してかまいません。