Java 在JUnit测试中模拟按键

Java 在JUnit测试中模拟按键,java,swing,junit,Java,Swing,Junit,我完全被困在java测试中;它是关于通过测试方法将字符“a”发送到JFrame组件的JTextField JFrame类实现KeyListener接口,因此覆盖KeyPressed、KeyTyped和KeyRelease。同时,我将JTextField的所有按键转移到JFrame;在JFrame构造函数中,我有: JTextField txf_version = new JTextField(); txf_version.addKeyListener(this); 我想测试这个行为,然后在JT

我完全被困在java测试中;它是关于通过测试方法将字符“a”发送到JFrame组件的JTextField

JFrame类实现KeyListener接口,因此覆盖KeyPressed、KeyTyped和KeyRelease。同时,我将JTextField的所有按键转移到JFrame;在JFrame构造函数中,我有:

JTextField txf_version = new JTextField();
txf_version.addKeyListener(this);
我想测试这个行为,然后在JTextField中模拟a型角色的动作

我所有的尝试都失败了;我尝试使用java.awt.Robot类,如下所示:,但我得到一个奇怪的行为:调用

robot.keyPress(KeyEvent.VK_A);
直接在我的IDE中显示字符,而不是在虚拟JFrame中!尝试使用requestFocus()或requestFocusInWindow()无效

我还尝试了KeyEvents:

KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_PRESSED, System
        .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
bookWindow.txf_version.dispatchEvent(key);
但是textfield的文本属性也没有改变

以下是我目前的方法:

@Test
void testBtnSaveChangesBecomesRedWhenVersionChanged() throws AWTException,
      InterruptedException, NoSuchFieldException, IllegalAccessException {
  initTest();

  KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_PRESSED, System
        .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
  bookWindow.txf_version.dispatchEvent(key);

  System.out.println("dans txf_version : " + bookWindow.txf_version.getText
        ());

  assertEquals(Color.RED, bookWindow.getBtnSaveChangesForegroundColor());


}
我可以通过在JFrame的子类中编写main()方法来了解实际行为,但我认为了解如何模拟swing组件测试的键是很有用的

多谢各位

编辑: 我根据AJNeufeld的答案修改了测试代码,但仍然不起作用。以下是我的测试代码:

@Test
void testBtnSaveChangesBecomesRedWhenVersionChanged() throws AWTException,
      InterruptedException, NoSuchFieldException, IllegalAccessException,
      InvocationTargetException {
//bookEditor2 & bookWindow
SwingUtilities.invokeAndWait(() -> {
  bookWindow = new BookWindow();
  VectorPerso two = new VectorPerso();
  two.add(le_livre_de_la_jungle);
  two.add(elogeMaths);
  bookWindow.setTableDatas(two);
  bookWindow.table.setRowSelectionInterval(1, 1);
  bookWindow.txf_version.requestFocusInWindow();
  KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_TYPED, System
          .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
  bookWindow.txf_version.dispatchEvent(key);
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  System.out.println("dans txf_version : " + bookWindow.txf_version.getText
          ());

  assertEquals(Color.RED, bookWindow.getBtnSaveChangesForegroundColor());
});


}
plintln行在控制台中生成一个文本:“dans txf_版本:0”,表示密钥未发送到txf_版本

编辑2:

新尝试:

@Test
  void testBtnSaveChangesBecomesRedWhenVersionChanged() throws AWTException,
          InterruptedException, NoSuchFieldException, IllegalAccessException,
          InvocationTargetException {
    //bookEditor2 & bookWindow
    SwingUtilities.invokeAndWait(() -> {
      bookWindow = new BookWindow();
      VectorPerso two = new VectorPerso();
      two.add(le_livre_de_la_jungle);
      two.add(elogeMaths);
      bookWindow.setTableDatas(two);
      bookWindow.table.setRowSelectionInterval(1, 1);
      bookWindow.txf_version.requestFocusInWindow();
      KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_TYPED, System

              .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
      bookWindow.txf_version.dispatchEvent(key);
    });
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    SwingUtilities.invokeAndWait(() -> {
      System.out.println("dans txf_version : " + bookWindow.txf_version.getText
              ());

      assertEquals(Color.RED, bookWindow.getBtnSaveChangesForegroundColor());
    });


  }

哟,我想你有两个选择

1) 仍然需要使用SWING来找到解决方案(但在这种情况下,我没有经验,也不知道如何帮助您)

2) 它使用SikulliJava框架来测试桌面应用程序。您可以添加

  • 制作UI元素的屏幕截图,并将它们放入应用程序的测试数据文件夹中 使用sikuli+JUnit
  • 编写一个简单的测试,在其中为按钮的pic设置一个路径(例如)
  • 写下动作,移动,点击或者你真正需要的东西。 在非常简单的情况下,这将是

    按钮按钮=新按钮(“测试文件夹/pic1.jpg”)

    按钮。单击()

运行之后,您将看到,您的光标被移动到按钮上,并单击它


有关更多详细信息,请在web上查找有关Sikulli+Java的示例。

我认为您在做一些错误的事情,但如果没有完整的示例,则很难判断

首先,
JTextField
实际上与按下的
键事件无关。它与键入的
事件有关

其次,Swing在事件调度线程(EDT)上处理事件,它不一定是JUnit要运行的线程。当你不参加EDT时,你真的不应该改变事情。我不确定
eventDispatch()
是否切换到EDT。可能吧。但它也可以使用
invokeLater()
,在这种情况下,执行会立即传递到
assertEquals()
,该过程会失败,因为事件处理尚未发生

下面是一个最小的、完整的、可验证的示例,其中显示了一个发送的按键,它改变了按钮的颜色,以及一个JUnit测试用例,它检查并通过:

首先,测试中的代码:

public class SwingUnitTesting extends JFrame {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(SwingUnitTesting::new);
    }

    JTextField tf = new JTextField();
    JButton btn = new JButton("Test Button");

    SwingUnitTesting() {
        add(tf, BorderLayout.PAGE_END);
        add(btn, BorderLayout.PAGE_START);

        tf.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                btn.setForeground(Color.RED);
            }
        });

        setSize(200, 80);
        setLocationByPlatform(true);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setVisible(true);
    }
}
以及单元测试:

public class SwingUnitTestingTest {

    SwingUnitTesting sut;

    @Before
    public void setUp() throws Exception {
        SwingUtilities.invokeAndWait(() -> {
            sut = new SwingUnitTesting();
        });
    }

    @Test
    public void btnNotRedBeforeKeypress() throws InvocationTargetException, InterruptedException {
        SwingUtilities.invokeAndWait(() -> {
            assertNotEquals(Color.RED, sut.btn.getForeground());
        });
    }

    @Test
    public void btnRedAfterKeypress() throws InvocationTargetException, InterruptedException {
        SwingUtilities.invokeAndWait(() -> {
            sut.tf.requestFocusInWindow();
            sut.tf.dispatchEvent(new KeyEvent(sut.tf,
                    KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0,
                    KeyEvent.VK_UNDEFINED, 'A'));
            assertEquals(Color.RED, sut.btn.getForeground());
        });
    }
}
在运行swing测试时,您可能会使用一些JUnit
@Rule
技巧或自定义运行程序来自动更改为EDT


更新

我感到好奇,并试图找到一个现有的
@规则
,它将
@放在
@之前
@测试
,以及
@之后
代码放在EDT上,但我的Google fu让我失望了; 我知道我以前见过,但我找不到。 最后,我创建了自己的:

public class EDTRule implements TestRule {

    @Override
    public Statement apply(Statement stmt, Description dscr) {
        return new Statement() {
            private Throwable ex;

            @Override
            public void evaluate() throws Throwable {
                SwingUtilities.invokeAndWait(() -> {
                    try {
                        stmt.evaluate();
                    } catch (Throwable t) {
                        ex = t;
                    }
                });
                if (ex != null) {
                    throw ex;
                }
            }
        };
    }
}
使用此规则,JUnit测试变得更简单:

public class SwingUnitTestingTest {

    @Rule
    public TestRule edt = new EDTRule();

    SwingUnitTesting sut;

    @Before
    public void setUp() throws Exception {
        sut = new SwingUnitTesting();
    }

    @Test
    public void btnNotRedBeforeKeypress() {
        assertNotEquals(Color.RED, sut.btn.getForeground());
    }

    @Test
    public void btnRedAfterKeypress() {
        sut.tf.requestFocusInWindow();
        sut.tf.dispatchEvent(
                new KeyEvent(sut.tf, KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'A'));
        assertEquals(Color.RED, sut.btn.getForeground());
    }
}


在MacOS上使用jdk1.8.0_121测试,最近我不得不测试一个定制的KeyAdapter,并创建了一个定制的JTextField来调度密钥事件。我使用的一段代码如下:

public class ProcessKeyOnTextFieldTest {

    @Test
    public void pressKeyTest() throws Exception {
        JTextFieldWithTypedKeySupport textField = new JTextFieldWithTypedKeySupport();

        textField.pressKey('a');
        textField.pressKey('b');

        assertEquals("ab", textField.getText());
    }

    class JTextFieldWithTypedKeySupport extends JTextField {

        int timestamp;

        void pressKey(char key) throws InvocationTargetException, InterruptedException {
            SwingUtilities.invokeAndWait(() -> super.processKeyEvent(createEvent(key)));
        }

        KeyEvent createEvent(char keyChar) {
            return new KeyEvent(this, KeyEvent.KEY_TYPED, timestamp++, 0, KeyEvent.VK_UNDEFINED, keyChar);
        }
    }
}

你好,谢谢你的回答。我不知道JUnit中的规则,所以我尝试在没有规则的情况下修改您的答案,但仍然存在一个问题。我将所有测试代码放在Swing线程中,但是textField的文本没有更改。请看一下我的编辑。你不能(有效地)在EDT中睡觉。Swing在EDT中处理事件,因此如果您休眠该线程,则不会处理任何内容。相反,您需要如下内容:
invokeAndWait(()->{…});Thread.sleep();调用EANDWAIT(()->{…})。您可以根据需要多次调用
invokeAndWait
。请看一下我的问题,我已经更新了,但仍然不起作用。它应该可以工作,我真的不明白为什么会有错误。textfield包含其默认值0,即使在注入keyEvent之后,至少是预期要执行此操作的语句。完成!我在JFrame类中进行了搜索,当我在构造函数中添加一些关于窗口的内容时,它确实起了作用:setLocationByPlatform(true)、setLocation(0,0)、setSize(100,50)、setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)和几乎setVisible(true)。无头测试显然不适用于这种方法!再次感谢。谢谢,这是一个有趣的工具sikuli,但我认为该网站似乎已关闭。然而,我更愿意让这个项目无头地测试我的JFrame,让它更简单。