Automated Testing of Java Swing Applications



    Good day! A year and a half ago, my team had to test a Java Swing application that could have different visualizations spanned by a common process. There were few articles on this topic at that time, there were no concrete solutions at all. TestComplete and other scripting technologies (for example, supporters of TestComplete forgive me) did not want to use, because the application should have a flexible architecture, expandable and changeable as part of the Agile process.

    A day's search on Google, an analysis of dozens of examples and technologies led me to two possible options:
    • Fest
    • Jemmy

    Without diving into the depths of the depths of comparison, I chose the Fest library. With its help and, of course, Junit, Mockito, we started testing our application. I’ll talk about this below.

    Of course, you can start by reading the documentation from Getting Started, and use the work through FrameFixture - everything is well described here , but if tasks go beyond searching / clicking on standard Java Swing components, then below, as an example of specific tasks, I will show how it's done.

    Let's start with the robot, namely org.fest.swing.core.Robot:
    Robot robot = BasicRobot.robotWithCurrentAwtHierarchy();
    

    The robot can click components, focus on them, work with the mouse cursor, and perform various checks. But we will start with the implementation of ComponentFinder, which the robot kindly provides to us. And to make it interesting, consider everything with an example. In this article I will not offer architectural solutions, offer the implementation of the PageObject pattern for Swing, but rather try to just explain with an example how to use the library. You can already come up with your own architecture - the tasks are always different.

    Let's say we have a JFrame with three custom buttons. Suppose you want to find them, check that the texts on them are "Buy", "Sell", "Delete", respectively, and that the button is "Delete"! IsEnabled ().

    For this we need:
    ComponentFinder finder = robot.finder();
    

    It has many methods, such as findByLabel, findByName. But we will turn our attention to:
    • finder.find (ComponentMatcher matcher) - returns Component
    • finder.findAll (ComponentMatcher matcher) - returns Collection.

    There are options for methods 1 and 2 with passing a generic matcher - sometimes it is useful. But since we usually know what we are looking for, we mainly use 1 and 2.

    The ComponentMatcher interface is essentially one method:
    boolean matches(Component c)
    

    We need an implementation to search for our custom button in the text on it:
    public class CaptionMatcher implements ComponentMatcher {
        private String expectedCaption;
        private String actualComponentCaption;
    public CaptionMatcher(String expectedCaption) {
        this.expectedCaption = expectedCaption;
    }
    @Override
    public boolean matches(Component comp) {
        if (comp != null && expectedCaption != null && comp instanceof CustomButton) {
            actualComponentCaption = ((CustomButton) comp).getText();
            if (expectedCaption.equals(actualComponentCaption)) {
                return true;
            }
        }
        return false;
    }
    public String getExpectedCaption() {
        return expectedCaption;
    }
    public void setExpectedCaption(String expectedCaption) {
        this.expectedCaption = expectedCaption;
    }
    }
    

    Now try to find our first button:
    finder.find(new CaptionMatcher("Купить"));
    

    If it is not found, then a ComponentLookupException will be returned. We can also find the second button and the third, changing the expectedCaption of the match. With the third button, you can do different things. If you often have to check the "backlash" of the buttons, then you should overload the CaptionMatcher constructor and add the enable / disable check to matches.

    If rare, then you can just like this:
    CustomButton btnDel = (CustomButton) finder.find(new CaptionMatcher("Удалить"));
    Assert.assertTrue(!btnDel.isEnabled());
    

    One could go another way - make a ClassMatcher, in which matches returns true if the instanceof component is CustomButton. And search using findAll. Then we would get all the buttons and then, having run through them, would bring to the class of our buttons and check everything that is needed.

    At this point, one remark is absolutely necessary - no one has canceled the threads for Swing rendering, and if you start looking for a component before it has time to render, you will get a ComponentLookupException. Therefore, best practice, in my opinion, is to wrap the component search in a loop and search for a component with some timeout, intercepting all the search errors. If the component was not found for the selected timeout, then assume that it is not there and throw a ComponentLookupException above.

    We learned how to find the components. Now about the clicks. Experience has shown that it is better to toss an ActionEvent directly to a component than to try to use the mouse using a robot. Firstly, with this approach, you can safely continue to work while the tests are chasing, and secondly, it is faster to throw an event than to click. We have this event implemented at the level of the component itself - designed as a public doClick () method.

    In order to demonstrate Fest in action:

    1. Below is a link to turnips - for clarity, I sketched several tests that check addition, subtraction and multiplication in a calculator. I draw attention to the fact that Thread.sleep (100) is inserted between the clicks;
    To speed up, it is better to somehow arrange the "expected" result of the action. Not only in the calculator, but also in any other project - any action is desirable to associate with the expected result.

    bitbucket.org/stovmasyan/calcexample/

    Here is a video of launching these tests:



    2. And here is a video on how our product is automatically tested - self-checkout checkout . Everything is based on actions and expected results. You will not even have time to see some screens and components - nevertheless they manage to be checked along and across:


    Also popular now: