Автотесты в функциональном стиле

Элементы функционального программирования появились в Java сравнительно недавно, но приобретает все большую популярность. Особенно в части stream API — наверное нет Java разработчика, который бы не слышал/читал/применял этот API для работы с коллекциями. К сожалению, большинство и не идет дальше использования Stream API, тогда как функциональный подход позволяет значительно упростить жизнь разработчикам автотестов. Ниже я расскажу про два примера такого упрощения — словари проверок и специализированные матчеры


Словари проверок.

Если Вы используете BDD подход, то наверняка, применяли параметризированные шаги проверки.

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

Для реализации шага такой проверки с использованием ООП/процедурного подхода можно применить набор методов и switch для определения имени поля для проверки:

 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("Неожиданное поле");
        }

В коде выше нет ничего плохого, он хорошо структурирован. Но у него есть проблема — трудоемкое добавление еще одной проверки: нужно, во-первых, реализовать проверку, а, во-вторых, добавить ее в свитч. Второй шаг представляется избыточным. Если применить «словарь проверок», то можно обойтись только первым шагов.
Словарь проверок — это Map, в которой ключом является имя проверки, а значением — функция, которая принимает в качестве параметров запись из БД, а возвращает Boolean. То есть java.util.function.Predicate

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

Переписываем проверки:

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

Переписываем метод вызова проверок:

    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));
    }

Что происходит во фрагменте выше: Пытаемся получить проверку из по имени поля Optional.ofNullable (checkMap.get (name)), если она NULL, то выбрасываем исключение. Иначе выполняем полученную проверку.
Теперь для того, чтобы добавить новую проверку ее достаточно добавить в словарь. В методе вызова проверок она становится доступна автоматически. Полный исходный код примера и другие примеры исполльзования ФП для автотестов доступны в репозитории на GitHub:
https://github.com/kneradovsky/java8fp_samples/


Custom Matchers

Практика показывает, что ассерты достаточно редко применяются в автотестах Selenuim WebDriver. На мой взгляд, наиболее вероятной причиной этого является то, что стандартные Matchers не предоставляют функциональности для проверки состояния WebElement. Почему нужно применять assertions? Это стандартный механизм, который поддерживается любыми средствами генерации отчетов и представления результатов тестирования. Зачем изобретать велосипед, если можно его доработать.
Как функциональный подход может сделать использование assertions для проверки свойств и состояние WebElement«ов удобным? И зачем ограничиваться только веб элементами?
Представим, что у нас есть функция, которая принимает 2 аргумента: сообщение об ошибке в случае провала и функцию-предикат (принимает проверяемый WebElement и возвращает результат проверки), а возвращает 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);
            }
        };
    }

Это позволяет конструировать любые проверки со специализированными сообщениями об ошибке.

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

Но зачем ограничивать себя только веб элементами? Сделаем статический generic метод, который примет тип объекта, вернет функцию от 2 аргументов: сообщение об ошибке в случае провала и функция-предикат (принимает объект заданного тип и возвращает результат проверки) и возвращающую 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);
            }
        };
    }

Теперь у нас есть специализированный матчер, который можно применять в assert«aх:

    BiFunction, BaseMatcher>> webElMatcherSupp = typedMatcher2(WebElement.class);

BaseMatcher shouldBeTable = apply("Should be Table",e->e.getTagName().equalsIgnoreCase("table"));

    assertThat(elem2Test,shouldBeTable);

В комбинациях с другими матчерами:

assertThat(elem2test,not(shouldBeTable));

Или так

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

assertThat(elem2test,allOf(not(shouldBeTable),hasText1));

Кроме этого матчеры можно использовать в допущениях (assumptions)

assumeThat(elem2test,not(shouldBeTable));

Но и это еще не все. Можно создать параметризованный специализированный матчер:

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")));

Таким образом получаем специализированный матчер, у которого сообщение и проверка параметризуется любым значением, передаваемым на этапе выполнения.

Заключение:
Применение функционального подхода в разработке автотестов позволяет с одной стороны сократить количество кода, с другой — повысить его читаемость. Собственные матчеры позволяют легко создавать наборы типизированных проверок, дополняя стандартный механизм assertions. Словари проверок избавляют от необходимости выполнения ненужной работы.
Полный код примеров находится в репозитории: https://github.com/kneradovsky/java8fp_samples/

© Habrahabr.ru