Паттерны для тестировщиков: proxy
2026-03-10 02:32 Diff

Главная идея паттерна proxy (в русскоязычных изданиях его называют «заместитель») состоит в том, чтобы выдать для работы не реальный объект, а подмену, которая использует методы объекта и нашу логику, если мы такую добавили.

Как это делается

1) Создаём интерфейс с публичными методами объекта, который хотим подменить. 2) Создаём класс, который: — реализует этот интерфейс; — имеет доступ к оригинальному объекту, чтобы вызывать его методы. 3) Добавляем в методы созданного класса свою логику.

Практика

На практике должно быть понятней. Дано: есть у нас WebDriver. И есть у него метод findElements(By by); Задача: мне очень сильно нужно логировать, сколько элементов было найдено по селектору.

Каждый раз, когда я вызываю метод:

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

Всё, что делает метод, — вызывает get() у настоящего драйвера. А вот метод:

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

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

Теперь, когда мы знаем на один паттерн больше, напомню, что когда в руке молоток, всё вокруг кажется гвоздями. Просто помните об этом.