Автоматизированное тестирование в разных окружениях

JQA_Deep_4.6_site-5020-940a13.png

Вот у меня есть тест. Очень простой: 1. Открываю Яндекс. 2. Пишу «отус». 3. Проверяю, что первый результат — это ссылка на сайт.

public class test1 {
    private WebDriver driver;
    @Before
    public void setUp() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    }

    @Test
    public void test() {
        driver.get("https://yandex.ru");
        driver.findElement(cssSelector("#text")).sendKeys("otus", Keys.ENTER);
        String text = driver
                .findElement(cssSelector("[aria-label='Результаты поиска']"))
                .findElement(cssSelector("li h2"))
                .getText();

        assertEquals(text, "OTUS - Онлайн-образование – Профессиональные курсы");
    }

    @After
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Но я хочу выполнять тестирование на странице yandex.ru и на странице ya.ru.

Пример, конечно, такой себе, но если представить, что вместо yandex.ru и ya.ru у нас, скажем, какие-нибудь dev.myapp.com и preprod.myapp.com, то это вполне жизненная ситуация, когда мы хотим тестировать разные окружения.

А от окружений часто зависят и другие параметры: например, логин-пароль, подключения к БД, url для обращения по REST API и всё такое.

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

Итак, чтобы запустить тест на ya.ru, мне нужно внести изменения в код теста — поменять параметр у метода get().

 @Test
    public void test() {
        driver.get("https://ya.ru");
        driver.findElement(cssSelector("#text")).sendKeys("otus", Keys.ENTER);
        String text = driver
                .findElement(cssSelector("[aria-label='Результаты поиска']"))
                .findElement(cssSelector("li h2"))
                .getText();

        assertEquals(text, "OTUS - Онлайн-образование – Профессиональные курсы");
    }

Это сработает. Теперь я могу тестировать и yandex, и ya (dev и preprod).

Идея с редактированием кода проваливается, как только мы переносим тесты на CI: там редактировать код для каждого запуска не получится. Да и параметров может быть с десяток – в одном из них я обязательно ошибусь (я себя знаю), и весь прогон тестов будет завален.

Параметры

Любой, кто в Java дольше 1,5 часов, знает, что можно задавать параметры.

public class test2 {

    private WebDriver driver;
    private String url;
    private long timeout;

    @Before
    public void setUp() {
        WebDriverManager.chromedriver().setup();
        url = System.getProperty("test.url", "https://yandex.ru");
        timeout = Long.valueOf(System.getProperty("test.timeout", "10"));

        driver = new ChromeDriver();
        driver.manage().timeouts().implicitlyWait(timeout, TimeUnit.SECONDS);
    }

    @Test
    public void test() {
        driver.get(url);
        driver.findElement(cssSelector("#text")).sendKeys("otus", Keys.ENTER);
        String text = driver
                .findElement(cssSelector("[aria-label='Результаты поиска']"))
                .findElement(cssSelector("li h2"))
                .getText();

        assertEquals(text, "OTUS - Онлайн-образование – Профессиональные курсы");
    }

    @After
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Теперь есть параметры: url и timeout. Их я могу задавать при запуске, добавив в команду запуска -Dtest.url=https://ya.ru. Параметр test.timeout я не стал задавать – тогда будет использовано дефолтовое значение (10).

Чуточку лучше. Код написан один раз и я могу смело запускать его на CI-сервере или ещё где. Теперь мне остаётся сконфигурировать два разных запуска: один – -Dtest.url=https://yandex.ru, второй – -Dtest.url=https://ya.ru.

Всё ещё проблемы с тем, что у меня может быть десяток параметров – как-то некомфортно. А если появится еще вариант для тестирования (dev/preprod/uat/test)?

Да и вообще, раз я храню в гите код тестов, почему бы не хранить и партеры, с которыми я этот код гоняю.

В общем, жизнь показывает, что лучше всего хранить конфиги в файлах .properties.

Файлы

Идея такая: в один файл common.properties я вынесу общие для любого окружения свойства.

common.properties

timeout=10

А в testYa.properties и testYandex.properties положим то, что различается.

testYa.properties

test.url=https://ya.ru

testYandex.properties

test.url=https://yandex.ru

Тест выглядит вот так:

public class test3 {

    private WebDriver driver;
    private PropertiesResolver config;

    @Before
    public void setUp() {
        WebDriverManager.chromedriver().setup();
        config = new PropertiesResolver();
        driver = new ChromeDriver();
        driver.manage().timeouts().implicitlyWait(Long.valueOf(config.getProperty("timeout")), TimeUnit.SECONDS);
    }

    @Test
    public void test() {
        driver.navigate().to(config.getUrl());
        driver.findElement(cssSelector("#text")).sendKeys("otus", Keys.ENTER);
        String text = driver
                .findElement(cssSelector("[aria-label='Результаты поиска']"))
                .findElement(cssSelector("li h2"))
                .getText();

        assertEquals(text, "OTUS - Онлайн-образование – Профессиональные курсы");
    }

    @After
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Только вот класс PropertiesResolver нужно реализовать самому:

class PropertiesResolver {
    private final Properties properties = new Properties();

    PropertiesResolver() {
        String commonPropsFile = "common.properties";
        String envPropsFile = System.getProperty("propertiesFile");

        loadProperties(commonPropsFile);
        loadProperties(envPropsFile);
    }

    String getProperty(String name) {
        return properties.getProperty(name);
    }

    URL getUrl(){
        try {
            return new URL(properties.getProperty("test.url"));
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void loadProperties(String fileName) {
        InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);

        try {
            properties.load(stream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Здесь мы говорим, что хотим выгрузить все свойства из файла common.properties и из файла, который будет задан при запуске в параметре -DpropertiesFile.

Уже лучше — храним конфиги в файлах. Запускаем всего с одним параметром — именем файла.

Только вот, что неприятно, так это это, что теперь все параметры — это строки. И нужно самому крутиться с тем, чтобы привести параметр к нужному типу:

driver.manage().timeouts().implicitlyWait(Long.valueOf(config.getProperty("timeout")), TimeUnit.SECONDS);

Или писать обёртку в классе PropertiesResolver для каждого не String-параметра:

URL getUrl(){
        try {
            return new URL(properties.getProperty("test.url"));
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return null;
    }

Owner

Очень удачно с этим справляется Owner.

Добавим зависимость:

<dependency>
    <groupId>org.aeonbits.owner</groupId>
    <artifactId>owner-java8</artifactId>
    <version>1.0.10</version>
</dependency>

И создадим интерфейс, расширяющий Config:

@Sources({"${propertiesFile}", "classpath:common.properties"})
public interface TestConfig extends Config {
    @DefaultValue("10")
    long timeout();

    @Key("test.url")
    URL url();
}

Здесь интересны следующие моменты:

  • @Sources({"${propertiesFile}", "classpath:common.properties"}) — формирует список файлов, откуда читать проперти. Из файла common.properties и из файла, переданного в параметре propertiesFile;
  • аннотация @Key() определяет имя параметра, хранящего значение. Аннотация необязательна. Только если имя параметра не совпадает с именем метода в интерфейсе;
  • @DefaultValue — это дефолт вэлью, что тут добавишь?

Теперь тест выглядит вот так:

public class test4 {

    private WebDriver driver;
    private TestConfig config;


    @BeforeClass
    public static void beforeClass() {
        WebDriverManager.chromedriver().setup();
    }

    @Before
    public void setUp() {
        config = ConfigFactory.create(TestConfig.class);
        driver = new ChromeDriver();
        driver.manage().timeouts().implicitlyWait(config.timeout(), TimeUnit.SECONDS);
    }

    @Test
    public void test() {
        driver.navigate().to(config.url());
        driver.findElement(cssSelector("#text")).sendKeys("otus", Keys.ENTER);
        String text = driver
                .findElement(cssSelector("[aria-label='Результаты поиска']"))
                .findElement(cssSelector("li h2"))
                .getText();

        assertEquals(text, "OTUS - Онлайн-образование – Профессиональные курсы");
    }

    @After
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Приведение к нужным типам происходит автоматически — не нужно на это отвлекаться. Все переменные вынесены в конфиг-файлы — их удобно хранить, читать, редактировать и версионировать.

Вот как-то так. Удобный инструмент для работы с конфигами. Инструмент стоит того, чтобы добавить его в свою копилку полезных инструментов и, как минимум, почитать, что он ещё может.

Понравилась статья? Пишите в комментариях!

Автор
0 комментариев
Для комментирования необходимо авторизоваться