如何入门:使用AssertJ Swing测试Java Swing GUI

时间:2017-10-10 22:01:06

标签: java swing ui-testing assertj

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

answer (on "What is the best testing tool for Swing-based applications?")建议使用FEST,但遗憾的是已停止使用answer。 然而,有一些项目从FEST离开的地方继续。其中一个(在AssertJ中提到)引起了我的注意,正如我之前在单元测试中使用的那样:AssertJ Swing

显然有GitHub,它基于FEST并提供了一些易于使用的编写Swing 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;
    }
}

我意识到这个问题本身非常广泛,因此我自己提供了一个答案 - 展示这个特定的例子。

1 个答案:

答案 0 :(得分:1)

TL; DR:示例项目可在{{3}}找到。

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

  1. 单元测试框架(例如此处为junit - 但也可以使用testng
  2. 匹配的AssertJ Swing库(例如此处assertj-swing-junit
  3. 看起来像这样(在pom.xml

    中)
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-swing-junit</artifactId>
        <version>1.2.0</version>
        <scope>test</scope>
    </dependency>
    

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

    /**
     * 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 (e.g. 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 <code>{@link Robot}</code>.
         *
         * @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<MainWindow>() {
    
                @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 <strong>last action</strong> during {@link #setUp()}.
         */
        protected void onSetUp() {
            // default: everything is already set up
        }
    
        /*****************************************************************************************
         * Here you could insert further helper methods, e.g. frequently used component matchers *
         *****************************************************************************************/
    
        /**
         * Cleans up any resources used in this test. After calling <code>{@link #onTearDown()}</code>, this method cleans up resources used by this
         * test's <code>{@link Robot}</code>.
         *
         * @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 <strong>first action</strong> 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()调用,为了让你的生活更轻松。