Паттерны для тестировщиков: proxy
Главная идея паттерна proxy (в русскоязычных изданиях его называют «заместитель») состоит в том, чтобы выдать для работы не реальный объект, а подмену, которая использует методы объекта и нашу логику, если мы такую добавили.
Как это делается
1) Создаём интерфейс с публичными методами объекта, который хотим подменить. 2) Создаём класс, который: — реализует этот интерфейс; — имеет доступ к оригинальному объекту, чтобы вызывать его методы. 3) Добавляем в методы созданного класса свою логику.
Практика
На практике должно быть понятней.
Дано: есть у нас WebDriver. И есть у него метод
Каждый раз, когда я вызываю метод:
driver.findElements(By.cssSelector(".item"));
я хочу видеть в логах запись — сколько элементов было найдено.
Решение 1. В лоб
Чего уж там: я его применял. Просто берём и при каждом вызове пишем, сколько было найдено:
List<WebElement> 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<WebElement> 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<WebElement> findElements(By var1) { List<WebElement> 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); }
Всё, что делает метод, — вызывает
public List<WebElement> findElements(By var1) { List<WebElement> 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<WebElement> items = driver.findElements(By.cssSelector("a")); // => Selector By.cssSelector: a. Found 48 elements
Паттерн хорош, если нужно навесить логирование, кэширование, ленивую инициализацию, контроль доступа к методам. В общем, если вы пишете код и вам очень хочется, чтобы была какая-то прослоечка с вашими фичами, один из вариантов — прокси.
Теперь, когда мы знаем на один паттерн больше, напомню, что когда в руке молоток, всё вокруг кажется гвоздями. Просто помните об этом.