如何开始:使用AssertJ Swing测试Java Swing GUI

在使用Swing开发Java桌面应用程序时,我遇到了直接测试UI的需要,而不仅仅是通过unit testing来测试底层控制器/模型类。

这个答案(关于“什么是基于Swing的应用程序的最佳测试工具?”)建议使用FEST ,遗憾的是停止使用。 然而,有一些项目从FEST离开的地方继续。 一个特别的(在这个答案中提到)引起了我的注意,因为我之前在unit testing中使用它: AssertJ 。

显然有AssertJ Swing ,它基于FEST,提供了一些易于使用的编写Swing UI测试的方法。 但是,进入初始/工作设置很麻烦,因为很难说从哪里开始。


如何为以下示例UI创建最小测试设置,仅包含两个类?

约束:Java SE,Swing UI,Maven项目,JUnit

public class MainApp { /** * Run me, to use the app yourself. * * @param args ignored */ public static void main(String[] args) { MainApp.showWindow().setSize(600, 600); } /** * Internal standard method to initialize the view, returning the main JFrame (also to be used in automated tests). * * @return initialized JFrame instance */ public static MainWindow showWindow() { MainWindow mainWindow = new MainWindow(); mainWindow.setVisible(true); return mainWindow; } } 

 public class MainWindow extends JFrame { public MainWindow() { super("MainWindow"); this.setContentPane(this.createContentPane()); } private JPanel createContentPane() { JTextArea centerArea = new JTextArea(); centerArea.setName("Center-Area"); centerArea.setEditable(false); JButton northButton = this.createButton("North", centerArea); JButton southButton = this.createButton("South", centerArea); JPanel contentPane = new JPanel(new BorderLayout()); contentPane.add(centerArea); contentPane.add(northButton, BorderLayout.NORTH); contentPane.add(southButton, BorderLayout.SOUTH); return contentPane; } private JButton createButton(final String text, final JTextArea centerArea) { JButton button = new JButton(text); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { centerArea.setText(centerArea.getText() + text + ", "); } }); return button; } } 

我知道问题本身非常广泛,因此我自己提供了一个答案 – 显示 – 包含这个特定的例子。

TL; DR:示例项目可以在GitHub上找到 。


假设这是一个maven项目,您首先需要添加至少两个依赖项:

  1. unit testing框架(例如,这里是junit – 但也可以使用testng
  2. 匹配的AssertJ Swing库(例如这里assertj-swing-junit

它可能看起来像这样(在你的pom.xml

  junit junit 4.12 test   org.assertj assertj-swing-junit 1.2.0 test  

其次,我通常会选择一个基础测试类来将大多数测试设置与实际测试分开:

 /** * Base class for all my UI tests taking care of the basic setup. */ public class AbstractUiTest extends AssertJSwingTestCaseTemplate { /** * The main entry point for any tests: the wrapped MainWindow. */ protected FrameFixture frame; /** * Installs a {@link FailOnThreadViolationRepaintManager} to catch violations of Swing threading rules. */ @BeforeClass public static final void setUpOnce() { // avoid UI test execution in a headless environment (eg when building in CI environment like Jenkins or TravisCI) Assume.assumeFalse("Automated UI Test cannot be executed in headless environment", GraphicsEnvironment.isHeadless()); FailOnThreadViolationRepaintManager.install(); } /** * Sets up this test's fixture, starting from creation of a new {@link Robot}. * * @see #setUpRobot() * @see #onSetUp() */ @Before public final void setUp() { // call provided AssertJSwingTestCaseTemplate.setUpRobot() this.setUpRobot(); // initialize the graphical user interface MainWindow mainWindow = GuiActionRunner.execute(new GuiQuery() { @Override protected MainWindow executeInEDT() throws Exception { return MainApp.showWindow(); } }); this.frame = new FrameFixture(this.robot(), mainWindow); this.frame.show(); this.frame.resizeTo(new Dimension(600, 600)); onSetUp(); } /** * Subclasses that need to set up their own test fixtures in this method. Called as last action during {@link #setUp()}. */ protected void onSetUp() { // default: everything is already set up } /***************************************************************************************** * Here you could insert further helper methods, eg frequently used component matchers * *****************************************************************************************/ /** * Cleans up any resources used in this test. After calling {@link #onTearDown()}, this method cleans up resources used by this * test's {@link Robot}. * * @see #cleanUp() * @see #onTearDown() */ @After public final void tearDown() { try { onTearDown(); this.frame = null; } finally { cleanUp(); } } /** * Subclasses that need to clean up resources can do so in this method. Called as first action during {@link #tearDown()}. */ protected void onTearDown() { // default: nothing more to tear down } } 

实际的测试类可能如下所示:

 public class MainWindowTest extends AbstractUiTest { private JButtonFixture northButtonFixture; private JButtonFixture southButtonFixture; @Override protected void onSetUp() { this.northButtonFixture = this.frame.button(JButtonMatcher.withText("North")); this.southButtonFixture = this.frame.button(JButtonMatcher.withText("South")); } @Test public void testWithDifferingComponentMatchers() { // use JTextComponentMatcher.any() as there is only one text input this.frame.textBox(JTextComponentMatcher.any()).requireVisible().requireEnabled().requireNotEditable().requireEmpty(); this.northButtonFixture.requireVisible().requireEnabled().click(); // use value assigned in MainWindow class via JTextArea.setName("Center-Area") to identify component here this.frame.textBox("Center-Area").requireText("North, "); this.southButtonFixture.requireVisible().requireEnabled().click(); // write our own matcher JTextComponentFixture centerArea = this.frame.textBox(new GenericTypeMatcher(JTextArea.class, true) { @Override protected boolean isMatching(Component component) { return true; } }); centerArea.requireVisible().requireEnabled().requireText("North, South, "); } @Override protected void onTearDown() { this.northButtonFixture = null; this.southButtonFixture = null; } } 

一旦在项目中进行了这样的基本设置,您可能希望查看各种类型的组件匹配器,并可能在您要测试的各种组件上引入几个setName()调用,以便让您的生活有点容易。

Interesting Posts