Автоматизированное тестирование в разных окружениях
2026-03-10 16:42 Diff

Вот у меня есть тест. Очень простой: 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

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

testYa.properties

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

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

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