HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-03-10
1 <p>Вот у меня есть тест. Очень простой: 1. Открываю Яндекс. 2. Пишу "отус". 3. Проверяю, что первый результат - это ссылка на сайт.</p>
1 <p>Вот у меня есть тест. Очень простой: 1. Открываю Яндекс. 2. Пишу "отус". 3. Проверяю, что первый результат - это ссылка на сайт.</p>
2 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(); } } }<p>Но я хочу выполнять тестирование на странице<a>yandex.ru</a>и на странице<a>ya.ru</a>.</p>
2 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(); } } }<p>Но я хочу выполнять тестирование на странице<a>yandex.ru</a>и на странице<a>ya.ru</a>.</p>
3 <p>Пример, конечно, такой себе, но если представить, что вместо<strong>yandex.ru</strong>и<strong>ya.ru</strong>у нас, скажем, какие-нибудь<strong>dev.myapp.com</strong>и<strong>preprod.myapp.com</strong>, то это вполне жизненная ситуация, когда мы хотим<strong>тестировать разные окружения</strong>.</p>
3 <p>Пример, конечно, такой себе, но если представить, что вместо<strong>yandex.ru</strong>и<strong>ya.ru</strong>у нас, скажем, какие-нибудь<strong>dev.myapp.com</strong>и<strong>preprod.myapp.com</strong>, то это вполне жизненная ситуация, когда мы хотим<strong>тестировать разные окружения</strong>.</p>
4 <p>А от окружений часто зависят и другие параметры: например, логин-пароль, подключения к БД, url для обращения по REST API и всё такое.</p>
4 <p>А от окружений часто зависят и другие параметры: например, логин-пароль, подключения к БД, url для обращения по REST API и всё такое.</p>
5 <p>Но чтобы пример было легко воспроизвести, позволю себе такое вот допущение.</p>
5 <p>Но чтобы пример было легко воспроизвести, позволю себе такое вот допущение.</p>
6 <p>Итак, чтобы запустить тест на<strong>ya.ru</strong>, мне нужно внести изменения в код теста - поменять параметр у метода get().</p>
6 <p>Итак, чтобы запустить тест на<strong>ya.ru</strong>, мне нужно внести изменения в код теста - поменять параметр у метода get().</p>
7 @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 - Онлайн-образование - Профессиональные курсы"); }<p>Это сработает. Теперь я могу тестировать<strong>и yandex, и ya</strong>(dev и preprod).</p>
7 @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 - Онлайн-образование - Профессиональные курсы"); }<p>Это сработает. Теперь я могу тестировать<strong>и yandex, и ya</strong>(dev и preprod).</p>
8 <p>Идея с редактированием кода проваливается, как только мы переносим тесты на<strong>CI</strong>: там редактировать код для каждого запуска не получится. Да и параметров может быть с десяток - в одном из них я обязательно ошибусь (я себя знаю), и весь прогон тестов будет завален.</p>
8 <p>Идея с редактированием кода проваливается, как только мы переносим тесты на<strong>CI</strong>: там редактировать код для каждого запуска не получится. Да и параметров может быть с десяток - в одном из них я обязательно ошибусь (я себя знаю), и весь прогон тестов будет завален.</p>
9 <h2>Параметры</h2>
9 <h2>Параметры</h2>
10 <p>Любой, кто в Java дольше 1,5 часов, знает, что можно задавать параметры.</p>
10 <p>Любой, кто в Java дольше 1,5 часов, знает, что можно задавать параметры.</p>
11 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(); } } }<p>Теперь есть параметры:<strong>url</strong>и<strong>timeout</strong>. Их я могу задавать при запуске, добавив в команду запуска -Dtest.url=https://ya.ru. Параметр<strong>test.timeout</strong>я не стал задавать - тогда будет использовано дефолтовое значение (10).</p>
11 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(); } } }<p>Теперь есть параметры:<strong>url</strong>и<strong>timeout</strong>. Их я могу задавать при запуске, добавив в команду запуска -Dtest.url=https://ya.ru. Параметр<strong>test.timeout</strong>я не стал задавать - тогда будет использовано дефолтовое значение (10).</p>
12 <p>Чуточку лучше. Код написан один раз и я могу смело запускать его на CI-сервере или ещё где. Теперь мне остаётся сконфигурировать два разных запуска: один - -Dtest.url=https://yandex.ru, второй - -Dtest.url=https://ya.ru.</p>
12 <p>Чуточку лучше. Код написан один раз и я могу смело запускать его на CI-сервере или ещё где. Теперь мне остаётся сконфигурировать два разных запуска: один - -Dtest.url=https://yandex.ru, второй - -Dtest.url=https://ya.ru.</p>
13 <p>Всё ещё проблемы с тем, что у меня может быть десяток параметров - как-то некомфортно. А если появится еще вариант для тестирования (dev/preprod/uat/test)?</p>
13 <p>Всё ещё проблемы с тем, что у меня может быть десяток параметров - как-то некомфортно. А если появится еще вариант для тестирования (dev/preprod/uat/test)?</p>
14 <p>Да и вообще, раз я храню в гите код тестов, почему бы не хранить и партеры, с которыми я этот код гоняю.</p>
14 <p>Да и вообще, раз я храню в гите код тестов, почему бы не хранить и партеры, с которыми я этот код гоняю.</p>
15 <p>В общем, жизнь показывает, что лучше всего хранить конфиги в файлах<strong>.properties</strong>.</p>
15 <p>В общем, жизнь показывает, что лучше всего хранить конфиги в файлах<strong>.properties</strong>.</p>
16 <h2>Файлы</h2>
16 <h2>Файлы</h2>
17 <p>Идея такая: в один файл<strong>common.properties</strong>я вынесу общие для любого окружения свойства.</p>
17 <p>Идея такая: в один файл<strong>common.properties</strong>я вынесу общие для любого окружения свойства.</p>
18 <h3>common.properties</h3>
18 <h3>common.properties</h3>
19 <p>А в<strong>testYa.properties</strong>и<strong>testYandex.properties</strong>положим то, что различается.</p>
19 <p>А в<strong>testYa.properties</strong>и<strong>testYandex.properties</strong>положим то, что различается.</p>
20 <h3>testYa.properties</h3>
20 <h3>testYa.properties</h3>
21 <h3>testYandex.properties</h3>
21 <h3>testYandex.properties</h3>
22 test.url=https://yandex.ru<p>Тест выглядит вот так:</p>
22 test.url=https://yandex.ru<p>Тест выглядит вот так:</p>
23 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(); } } }<p>Только вот класс<strong>PropertiesResolver</strong>нужно реализовать самому:</p>
23 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(); } } }<p>Только вот класс<strong>PropertiesResolver</strong>нужно реализовать самому:</p>
24 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(); } } }<p>Здесь мы говорим, что хотим выгрузить все свойства из файла<strong>common.properties</strong>и из файла, который будет задан при запуске в параметре -DpropertiesFile.</p>
24 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(); } } }<p>Здесь мы говорим, что хотим выгрузить все свойства из файла<strong>common.properties</strong>и из файла, который будет задан при запуске в параметре -DpropertiesFile.</p>
25 <p>Уже лучше - храним конфиги в файлах. Запускаем всего с одним параметром - именем файла.</p>
25 <p>Уже лучше - храним конфиги в файлах. Запускаем всего с одним параметром - именем файла.</p>
26 <p>Только вот, что неприятно, так это это, что теперь все параметры - это строки. И нужно самому крутиться с тем, чтобы привести параметр к нужному типу:</p>
26 <p>Только вот, что неприятно, так это это, что теперь все параметры - это строки. И нужно самому крутиться с тем, чтобы привести параметр к нужному типу:</p>
27 driver.manage().timeouts().implicitlyWait(Long.valueOf(config.getProperty("timeout")), TimeUnit.SECONDS);<p>Или писать обёртку в классе<strong>PropertiesResolver</strong>для каждого не String-параметра:</p>
27 driver.manage().timeouts().implicitlyWait(Long.valueOf(config.getProperty("timeout")), TimeUnit.SECONDS);<p>Или писать обёртку в классе<strong>PropertiesResolver</strong>для каждого не String-параметра:</p>
28 URL getUrl(){ try { return new URL(properties.getProperty("test.url")); } catch (MalformedURLException e) { e.printStackTrace(); } return null; }<h2>Owner</h2>
28 URL getUrl(){ try { return new URL(properties.getProperty("test.url")); } catch (MalformedURLException e) { e.printStackTrace(); } return null; }<h2>Owner</h2>
29 <p>Очень удачно с этим справляется Owner.</p>
29 <p>Очень удачно с этим справляется Owner.</p>
30 <p>Добавим зависимость:</p>
30 <p>Добавим зависимость:</p>
31 &lt;dependency&gt; &lt;groupId&gt;org.aeonbits.owner&lt;/groupId&gt; &lt;artifactId&gt;owner-java8&lt;/artifactId&gt; &lt;version&gt;1.0.10&lt;/version&gt; &lt;/dependency&gt;<p>И создадим интерфейс, расширяющий Config:</p>
31 &lt;dependency&gt; &lt;groupId&gt;org.aeonbits.owner&lt;/groupId&gt; &lt;artifactId&gt;owner-java8&lt;/artifactId&gt; &lt;version&gt;1.0.10&lt;/version&gt; &lt;/dependency&gt;<p>И создадим интерфейс, расширяющий Config:</p>
32 @Sources({"${propertiesFile}", "classpath:common.properties"}) public interface TestConfig extends Config { @DefaultValue("10") long timeout(); @Key("test.url") URL url(); }<p>Здесь интересны следующие моменты:</p>
32 @Sources({"${propertiesFile}", "classpath:common.properties"}) public interface TestConfig extends Config { @DefaultValue("10") long timeout(); @Key("test.url") URL url(); }<p>Здесь интересны следующие моменты:</p>
33 <ul><li>@Sources({"${propertiesFile}", "classpath:common.properties"}) - формирует список файлов, откуда читать проперти. Из файла<strong>common.properties</strong>и из файла, переданного в параметре<strong>propertiesFile</strong>;</li>
33 <ul><li>@Sources({"${propertiesFile}", "classpath:common.properties"}) - формирует список файлов, откуда читать проперти. Из файла<strong>common.properties</strong>и из файла, переданного в параметре<strong>propertiesFile</strong>;</li>
34 <li>аннотация @Key() определяет имя параметра, хранящего значение. Аннотация необязательна. Только если имя параметра не совпадает с именем метода в интерфейсе;</li>
34 <li>аннотация @Key() определяет имя параметра, хранящего значение. Аннотация необязательна. Только если имя параметра не совпадает с именем метода в интерфейсе;</li>
35 <li>@DefaultValue - это дефолт вэлью, что тут добавишь?</li>
35 <li>@DefaultValue - это дефолт вэлью, что тут добавишь?</li>
36 </ul><p>Теперь тест выглядит вот так:</p>
36 </ul><p>Теперь тест выглядит вот так:</p>
37 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(); } } }<p>Приведение к нужным типам происходит автоматически - не нужно на это отвлекаться. Все переменные вынесены в конфиг-файлы - их удобно хранить, читать, редактировать и версионировать.</p>
37 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(); } } }<p>Приведение к нужным типам происходит автоматически - не нужно на это отвлекаться. Все переменные вынесены в конфиг-файлы - их удобно хранить, читать, редактировать и версионировать.</p>
38 <p>Вот как-то так. Удобный инструмент для работы с конфигами. Инструмент стоит того, чтобы добавить его в свою копилку полезных инструментов и, как минимум,<a>почитать</a>, что он ещё может.</p>
38 <p>Вот как-то так. Удобный инструмент для работы с конфигами. Инструмент стоит того, чтобы добавить его в свою копилку полезных инструментов и, как минимум,<a>почитать</a>, что он ещё может.</p>
39  
39