Прежде, чем вот это все


Привет. Я — Дима и я не знаю паттернов. Как для тестировщика, не сказать, что проблематично. Как для автоматизатора..? Ну, давайте честно, жить тоже можно.

Из чатиков, конференций и общения с коллегами, понятно: главный паттерн — PageObject — выучен, чего еще нужно?

А здесь вот был большой такой абзац размышлений о том, почему мы, на самом деле не используем шаблоны проектирования: мы их не знаем или на и без них неплохо? Еще были углубления в историю, что паттерны — это вам не это, а десятилетиями проверенные знания и методики, аргументы за и упоминание известных товарищей, которые против.

Но, в конечном счете, знание паттернов точно не помешает.

Так что, ни в коем случае не призывая вас к повсеместному вкручиванию шаблонов в код, начинаю публиковать цикл статей по паттернам.

Еще немножко о формате и поехали


Самих статей/книг/видеокурсов по паттернам вполне себе прилично. И я уверен, вы, без труда, найдете ресурс с грамотным объяснением, примерами на вашем любимом ЯП, юэмэляками и т.д.

Задача этой и последующих статей: рассказать о паттернах тем, кто уже работает с кодом, но не имеет хорошей базы по теории. Так что эти статьи, пожалуй, для меня, в первую очередь.

Ну и, поскольку, сам я — тестировщик, да и в моем круге общения немало таких же, рассказы будут с уклоном на автоматизацию тестирования.

Какие-то из паттернов покажутся странными и не очень полезными. А с какими-то вы, наоборот, обнаружите, что что-то подобное уже давно реализуете, только не знали, что это так называется.
В любом случае, я надеюсь, вы найдете для себя что-то полезное.

И да, я буду очень рад конструктивной критике в комментариях.

Вот.

  • про себя — рассказал
  • что будет — рассказал
  • зачем — рассказал
  • для кого и как — рассказал

Можно начинать.



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. Это и сделаем:

  1. Создаем интерфейс с публичными методами объекта, который хотим подменить
    В нашем примере, такой интерфейс уже есть — WebDriver.
  2. Создаем класс, который
    — реализует этот интерфейс
    — имеет доступ к оригинальному объекту, чтобы вызывать его методы
    
    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;
        }
    //остальные методы отрезал для краткости
    }
    
  3. Добавляем в методы созданного класса свою логику
    
    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



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

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

Комментарии (4)


  1. Nidhognit
    16.06.2018 14:47

    Хорошая статья, но больше похоже что вы используете патерн Декоратор


    1. EreminD Автор
      16.06.2018 16:55

      спасибо за ответ
      я, перед публикацией, тоже задумался, не декоратор ли?
      Поискав различия, пришел к тому

      Декоратор и Заместитель имеют схожие структуры, но разные назначения. Они похожи тем, что оба построены на композиции и делегируют работу другим объектам. Паттерны отличаются тем, что Заместитель сам управляет жизнью сервисного объекта, а обёртывание Декораторов контролируется клиентом.


  1. lxsmkv
    17.06.2018 16:08

    Да, автоматизатору паттерны не помешают. Было недавно дело, у меня приложение имеет две инкарнации, соответственно 2 варианта графического интерфейса. И вот стал я думать как на pageоbject фасад натянуть. Поднатужился — натянул. Смотрю — очень весело стало жить. Новые тесты писать перестались, а все время на создание совместимости кода для двух интерфейсов уходит. Задумался, а не фигню ли я делаю? И понял, что и вправду фигню. И разделил я тогда код на два проекта, и все встало на свои места.


    1. EreminD Автор
      18.06.2018 10:22

      штош… Следующую статью посвятим фасаду)