Functional style tests

    Functional programming elements have appeared in Java relatively recently, but are becoming increasingly popular. Especially in the stream API part - probably there is no Java developer who would not hear / read / use this API to work with collections. Unfortunately, the majority does not go further than using the Stream API, while the functional approach can significantly simplify the life of developers of autotests. Below I will talk about two examples of such simplification - check dictionaries and specialized matchers


    Verification dictionaries.


    If you use the BDD approach, then you probably applied the parameterized verification steps.


    Когда нажимаем на кнопку «Кнопка»
    Тогда проверить значения полей по БД
    |Поле1|
    |Поле2|
    |Поле3|

    To implement the step of such a check using the OOP / procedural approach, you can apply a set of methods and switch to determine the name of the field to check:


     private void checkMinBalanceAmount(String checkMinAmount) throws Exception {
            String minBalanceAmountStr = checkMinAmount;
            String minBalanceAmount = String.format("%.2f", Double.parseDouble(minBalanceAmountStr));
            String amountMinIF = amountLink.getText().replaceAll("(руб.|\\$|€)", "").replaceAll(" ", "");
            Assert.assertEquals(minBalanceAmount, amountMinIF);
        }
        private void checkMaxBalanceAmount(String checkMaxAmount) throws Exception {
            String maxBalanceAmountStr = checkMaxAmount;
            String maxBalanceAmount = String.format("%.2f", Double.parseDouble(maxBalanceAmountStr));
            String amountmaxIF = maxAmountDepositLink.getText().replaceAll("(руб.|\\$|€)", "").replaceAll(" ", "");
            Assert.assertEquals(maxBalanceAmount, amountmaxIF);
        }
        private void checkBalanceAmount(String checkBalanceAmount) throws Exception {
            String maxBalanceAmountStr = checkBalanceAmount;
            String maxBalanceAmount = String.format("%.2f", Double.parseDouble(maxBalanceAmountStr));
            String amountmaxIF = amountDepositLink.getText().replaceAll("(руб.|\\$|€)", "").replaceAll(" ", "");
            Assert.assertEquals(maxBalanceAmount, amountmaxIF);
        }
        public void проверяет_значение_поля(String name) throws Throwable {
            String query = "select * from deposit_and_account_data";
            List> fetchAll = Db.fetchAll(query, "main");
            switch (name) {
                case "Имя счета":
                    Assert.assertEquals(fetchAll.get(0).get("ACCOUNT_NAME"), nameDepositLink.getText());
                    break;
                case "Дата закрытия":
                    checkDate(fetchAll.get(0).get("CLOSE_DATE"));
                    break;
                case "Код валюты":
                    checkCurrency(fetchAll.get(0).get("NAME"));
                case "Сумма неснижаемого остатка":
                    checkMinBalanceAmount(fetchAll.get(0).get("MIN_BALANCE_AMOUNT"));
                    break;
                case "Максимальная сумма для снятия":
                    checkMaxBalanceAmount(fetchAll.get(0).get("MAX_SUM_AMOUNT"));
                    break;
                case "Сумма вклада":
                    checkBalanceAmount(fetchAll.get(0).get("BALANCE_AMOUNT"));
                    break;
                default:
                    throw new AutotestError("Неожиданное поле");
            }

    There is nothing wrong with the code above; it is well structured. But he has a problem - the time-consuming addition of one more check: first, you need to implement the check, and secondly, add it to the switch. The second step seems redundant. If you apply the "dictionary of checks", then you can do only the first steps.
    The checklist is a Map, in which the key is the name of the check, and the value is a function that takes a record from the database as parameters and returns a Boolean. That is java.util.function.Predicate


    Map>> checkMap = new HashMap<>();

    Rewriting checks:


    checkMap.put("Имя счета",exp -> exp.get("ACCOUNT_NAME").equals(nameDepositLink.getText()));

    We rewrite the method for invoking checks:


        public void проверяет_значение_поля(String name) throws Throwable {
            String query = "select * from deposit_and_account_data";
            Map expected = Db.fetchAll(query, "main").get(0);
            Assert.assertTrue(name,
                    Optional.ofNullable(checkMap.get(name))
                    .orElseThrow(()->new AutotestError("Неожиданное поле"))
                    .test(expected));
        }
    

    What happens in the fragment above: Trying to get a check from the field name Optional.ofNullable (checkMap.get (name)), if it is NULL, then throw an exception. Otherwise, we perform the obtained check.
    Now in order to add a new check it is enough to add it to the dictionary. In the method of invoking checks, it becomes available automatically. The full source code of the example and other examples of using the FP for autotests are available in the repository on GitHub:
    https://github.com/kneradovsky/java8fp_samples/


    Custom matchers


    Practice shows that assertions are rarely used in Selenuim WebDriver self-tests. In my opinion, the most likely reason for this is that the standard Matchers do not provide functionality for checking the status of WebElement. Why apply assertions? This is a standard mechanism that is supported by any means of generating reports and presenting test results. Why reinvent the wheel, if you can modify it.
    How can a functional approach make using assertions to check properties and state of WebElements convenient? And why be limited only to web elements?
    Imagine that we have a function that takes 2 arguments: an error message in case of failure and a predicate function (accepts the WebElement being checked and returns the result of the check), and returns Matcher.


        public static BiFunction, BaseMatcher> customMatcher = 
                (desc, pred) -> new BaseMatcher() {
                @Override
                public boolean matches(Object o) {
                    return pred.test((WebElement) o);
                }
                @Override
                public void describeTo(Description description) {
                    description.appendText(desc);
                }
            };
        }

    This allows you to design any checks with specialized error messages.


    BaseMacther m1 = customMatcher.apply("Результаты должны содержать qaconf.ru",e -> e.getAttribute("href").contains("qaconf.ru"));

    But why limit yourself to just web elements? We will create a static generic method that takes an object type, returns a function of 2 arguments: an error message in case of failure and a predicate function (accepts an object of a given type and returns a test result) and returns a Matcher.


        public static  BiFunction, BaseMatcher> typedMatcher2(Class cls) {
            return (desc, pred) -> new BaseMatcher() {
                @Override
                public boolean matches(Object o) {
                    return pred.test((T) o);
                }
                @Override
                public void describeTo(Description description) {
                    description.appendText(desc);
                }
            };
        }

    Now we have a specialized gamer that can be used in assert'ahs:


        BiFunction, BaseMatcher>> webElMatcherSupp = typedMatcher2(WebElement.class);
    BaseMatcher shouldBeTable = apply("Should be Table",e->e.getTagName().equalsIgnoreCase("table"));
        assertThat(elem2Test,shouldBeTable);

    In combination with other maths:


    assertThat(elem2test,not(shouldBeTable));

    Or so


    BaseMatcher hasText1 = webElMatcherSupp.apply("Should be contain text1",e->e.getText().equalsIgnoreCase("text1"));
    assertThat(elem2test,allOf(not(shouldBeTable),hasText1));

    In addition, matchers can be used in assumptions.


    assumeThat(elem2test,not(shouldBeTable));

    But that is not all. You can create a parameterized specialized matcher:


    Function textEquals = str -> webElMatcherSupp.apply("Text should equals to: " + str,e-> e.getText().equals(str));
    assertThat(elem2test,textEquals.apply("text2"));
    assertThat(elem2test,not(textEquals.apply("text3")));

    Thus, we obtain a specialized matcher, in which the message and check are parameterized by any value transmitted at the execution stage.


    Conclusion:
    The application of the functional approach in the development of autotests allows one to reduce the amount of code, on the other hand, to increase its readability. Own matchers make it easy to create sets of typed checks, complementing the standard assertions mechanism. Verification dictionaries eliminate the need for unnecessary work.
    The full code for the examples is in the repository: https://github.com/kneradovsky/java8fp_samples/


    Also popular now: