Паттерны для тестировщиков. Proxy (заместитель)
Прежде, чем вот это все
Привет. Я — Дима и я не знаю паттернов. Как для тестировщика, не сказать, что проблематично. Как для автоматизатора…? Ну, давайте честно, жить тоже можно.
Из чатиков, конференций и общения с коллегами, понятно: главный паттерн — PageObject — выучен, чего еще нужно?
А здесь вот был большой такой абзац размышлений о том, почему мы, на самом деле не используем шаблоны проектирования: мы их не знаем или на и без них неплохо? Еще были углубления в историю, что паттерны — это вам не это, а десятилетиями проверенные знания и методики, аргументы за и упоминание известных товарищей, которые против.
Но, в конечном счете, знание паттернов точно не помешает.
Так что, ни в коем случае не призывая вас к повсеместному вкручиванию шаблонов в код, начинаю публиковать цикл статей по паттернам.
Еще немножко о формате и поехали
Самих статей/книг/видеокурсов по паттернам вполне себе прилично. И я уверен, вы, без труда, найдете ресурс с грамотным объяснением, примерами на вашем любимом ЯП, юэмэляками и т.д.
Задача этой и последующих статей: рассказать о паттернах тем, кто уже работает с кодом, но не имеет хорошей базы по теории. Так что эти статьи, пожалуй, для меня, в первую очередь.
Ну и, поскольку, сам я — тестировщик, да и в моем круге общения немало таких же, рассказы будут с уклоном на автоматизацию тестирования.
Какие-то из паттернов покажутся странными и не очень полезными. А с какими-то вы, наоборот, обнаружите, что что-то подобное уже давно реализуете, только не знали, что это так называется.
В любом случае, я надеюсь, вы найдете для себя что-то полезное.
И да, я буду очень рад конструктивной критике в комментариях.
Вот.
- про себя — рассказал
- что будет — рассказал
- зачем — рассказал
- для кого и как — рассказал
Можно начинать.
Proxy — Прокси — Заместитель
Теория (чуть-чуть)
Паттерн прокси (в русскоязычных изданиях, Заместитель). Идея в том, чтобы выдать для работы не реальный объект, а подмену, которая использует методы объекта + нашу логику, если мы такую добавили. Все.
Как это делается:
1. Создаем интерфейс с публичными методами объекта, который хотим подменить
2. Создаем класс, который
— реализует этот интерфейс
— имеет доступ к оригинальному объекту, чтобы вызывать его методы
3. Добавляем в методы созданного класса свою логику
Практика
На практике должно быть понятней.
Дано: Есть у нас WebDriver. И есть у него метод findElements (By by);
Задача: Мне очень сильно нужно логировать, сколько элементов было найдено по селектору.
Каждый раз, когда я вызываю метод
driver.findElements(By.cssSelector(".item"));
я хочу видеть в логах запись — сколько элементов было найдено
Решение 1. В лоб. Чего уж там: я его применял.
Просто берем и, при каждом вызове пишем, сколько было найдено:
List items = driver.findElements(By.cssSelector(".item"));
logger.info("Found {} items", items.size());
Вариант нормальный, пока таких вызовов, ну, скажем, 7. Хотя уже неприятно и минусы такого подхода очевидны любому, кто хоть раз «слегка модифицировал свой код».
Решение 2. Используем прокси.
WebDriver — это интерфейс. Объявлены методы, но нет реализации. Реализацию содержат ChromeWebDriver, FirefoxWebDriver и т.д.
Нам, в тестах, не обязательно работать с каким-то конкретным классом для хрома или сафари. Нужно только, чтобы класс имплементил интерфейс WebDriver. Это и сделаем:
-
Создаем интерфейс с публичными методами объекта, который хотим подменить
В нашем примере, такой интерфейс уже есть — WebDriver. -
Создаем класс, который
— реализует этот интерфейс
— имеет доступ к оригинальному объекту, чтобы вызывать его методыpublic class LoggerWebDriver implements WebDriver{ private WebDriver driver; public void get(String s) { } public List
findElements(By by) { return null; } public WebElement findElement(By by) { return null; } //остальные методы отрезал для краткости } -
Добавляем в методы созданного класса свою логику
public class LoggerWebDriver implements WebDriver { private WebDriver driver; private final Logger logger = LogManager.getLogger(LoggerWebDriver.class); LoggerWebDriver() { //жестких ограничений по конструктору нет. //В идеале, он(и) должны повторять конструкторы объекта this.driver = new ChromeDriver(); } public void get(String var1) { driver.get(var1); } public List
findElements(By var1) { List items = driver.findElements(var1); logger.info("Selector {}. Found {} elements", var1.toString(), items.size()); return items; } public WebElement findElement(By var1) { return driver.findElement(var1); } //остальные методы отрезал для краткости }
Что произошло?
В класс добавлен
private WebDriver driver;
Это тот самый объект, который мы хотим подменить. Именно его методы мы будем вызывать дальше.
Теперь, можно посмотреть, например, на метод
public void get(String var1) {
driver.get(var1);
}
Все, что делает метод — вызывает get () у настоящего драйвера.
А вот метод
public List findElements(By var1) {
List items = driver.findElements(var1);
logger.info("Selector {}. Found {} elements", var1.toString(), items.size());
return items;
}
мы расширили в соответствии с нашей задачей.
Вот и все, можно юзать в тестах:
WebDriver driver = new LoggerWebDriver();
driver.get("http://google.com");
List items = driver.findElements(By.cssSelector("a"));
// => Selector By.cssSelector: a. Found 48 elements
Вот. Паттерн хорош, если нужно навесить логирование, кэширование, ленивую инициализацию, контроль доступа к методам. В общем, если вы пишете код и вам очень хочется, чтобы была какая-то прослоечка с вашими фичами, один из вариантов — прокси.
Теперь, когда мы знаем на один паттерн больше, напомню, что когда в руке молоток, все вокруг кажется гвоздями. Просто помните об этом.