Заметки за март 2015 года

Прокси из подручных средств

Мне по работе очень-очень редко бывает нужно протестировать сайты из разных сетей (в том числе и из-за рубежа). У меня есть доступ в эти сети только по SSH.

Я запускаю в терминале команду, чтобы проложить туннель:

ssh -N -D 10.1.1.1:44444 user@example.com
  • 10.1.1.1 — это адрес моего компьютера;
  • 44444 — порт, на котором будет создан туннель (может быть любым);
  • user@example.com — реквизиты для доступа к удалённому компьютеру.

Если в настройках браузера на любом компьютере в моей локальной сети указать адрес 10.1.1.1 и порт 44444 в разделе SOCKS-прокси, то все запросы будут проксироваться на удалённый компьютер.

В iOS, к сожалению, нельзя сразу сделать такие настройки. Там можно указать только адрес HTTP-прокси. Зато там можно указать адрес скрипта для автоматический конфигурации.

Я создаю файл proxy.pac с примерно таким содержимым:

function FindProxyForURL(url, host)
{ 
  return "SOCKS 10.1.1.1:44444";
}

И положу его на HTTP-сервер, который есть у меня на локальном компьютере. Проверяю, что файл отдаётся с правильным MIME-типом:


$ curl -Is http://10.1.1.1/proxy.pac | grep Content-Type
Content-Type: application/x-ns-proxy-autoconfig

Теперь я могу в настройках своего айФона указать URL для автоматической конфигурации прокси — http://10.1.1.1/proxy.pac . Иногда нужно выключить и включить WiFi, чтобы изменения быстрее вступили в силу.

Комментарии к заметке: 2

Шаблон тестирования Page Object

Функциональные тесты большинством воспринимаются как линейные сценарии: открой страничку, нажми эту кнопку, введи такой-то текст в поле ввода, нажми другую кнопку. Сценарии похожи на то, как действует реальный пользователь. Однако они плохи тем, что требуется повторять много однотипных действий в разных тестах, чтобы попасть в нужное исходное состояние. Если меняется содержимое станицы (например, разметка или классы у элементов), то нужно во все тестах внести соответствующие изменения.

В документации к Selenium описан другой подход — Page Object. Его особенность заключается в том, что страница представлена в виде модели с которой взаимодействует тест. Это уменьшает количество повторения кода. Доступ к элементам страницы осуществляется только в одном месте. Если поменяется разметка, то потребуется минимум изменений в коде Page Object, а тесты не нужно будет менять вообще.

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

Смотрите пример — https://gist.github.com/mistakster/597f110631fe8a5cde6b. Инфраструктура для запуска этого теста описана в заметке Node.js + mocha + selenium-webdriver.

Я тестирую главную страницу Яндекса. Она должна удовлетворять следующей спецификации:


Yandex home page
  √ should be valid (4857ms)
  √ should have at least 9 tabs above the search (4358ms)
  √ should have a weather widget (1562ms)

Сам тест выглядит очень просто и понятно. Вся магия скрыта в объекте Page и двух компонентах, которые должны быть на странице.

Когда мы открываем страницу, то не должны знать её адреса. Для разных окружений (локальная разработка, стейджинг, продакшин) он может быть разным. В тесте у Page Object просто вызывается соответствующий метод open(). Мы так же должны быть уверены, что страница открылась и на ней правильное содержимое. За это отвечает метод validate() . Итак, всю логику по взаимодействию с драйвером мы прячем в Page Object за компактным фасадом из методов.

Методы Page Object должны возвращать объекты других страниц или объекты компонент. Так осуществляются переход между страницами или в тесте можно получить доступ к отдельным блокам на странице. Например, в тесте можно получить список всех пунктов меню, которое расположено над поисковой строкой, или текущую погоду в вашем городе.

Комментарии к заметке: 2

Node.js + mocha + selenium-webdriver

Для запуска функциональных тестов нам понадобятся:

  1. Node.js — надеюсь, он у вас уже установлен;
  2. Python 2.7.х — указан в зависимостях модуля selenium-webdriver и должен быть установлен в систему до загрузки остальных пакетов;
  3. Java Runtime Environment — среда для запуска Selenium Server;
  4. mocha — с помощью этой утилиты мы будем запускать тесты; устанавливается командой npm i -g mocha;
  5. selenium-webdriver — через него мы будем оправлять Selenium-серверу команды и получать от него данные со страницы; устанавливается в папку проекта командой npm i selenium-webdriver;
  6. Selenium server — запускает и закрывает браузер, отправляет браузеру команды и т.п.; не требует особой установки; скачиваем JAR-файл в папку рядом с тестами;
  7. Driver Provider — вспомогательный класс, в котором сосредоточен код для запуска и остановки сервера и конфигурация драйвера (например, выбор браузера для тестирования).

Когда всё перечисленное будет установлено, можно начинать писать и запускать тесты.

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

var driverProvider = new DriverProvider();

Перед запуском пакета тестов выполняется метод before:

test.before(function () {
  return driverProvider.startUp();
});

По окончании работы тестов выполняется метод after:


test.after(function () {
  return driverProvider.tearDown();
});

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

В методах startUp() и tearDown() этого класса выполняются задачи по запуску и остановке соответствующих сервисов. Они должны возвращать promise-объект. По его состоянию будет определяться завершена-ли задача или нет.

Отличительной особенностью API selenium-webdriver является то, что большинство методов выполняются асинхронно. Однако, постоянно использоваться promise-объекты, чтобы обеспечить нужный порядок выполнения команд, вовсе не требуется. Для этого есть так называемые “control flow”. Внутри одного потока все команды будут исполняться синхронно по мере того как они были добавлены в поток. Это немного облегчает написание тестов. Но, если нужно получить значение из функции (например, список элементов, размеры элемента и т.д.), то без “обещаний” не обойтись.

Так же библиотека selenium-webdriver/testing тоже использует “control flow” чтобы последовательно исполнять содержимое тестов. Она дублирует методы mocha. Тест может вернуть promise-объект и он будет считаться завершённым, когда обещание будет установлено в какое-либо состояние. Если тест ничего не вернул, то он будет считаться завершенным, когда очередь заданий окажется пуста.

И в заключение команда, которая запустит на выполнение наш тест:

mocha page-spec.js

У команды mocha есть масса полезных параметров. Мне, например, нравятся отчёты в виде спецификации. Ещё можно указать максимальное время выполнения теста. Функциональные тесты, как правило, не быстрые и 2 секунды на тест (значение по-умолчанию) совсем не хватает, чтобы его выполнить.

В заключение, хочу обратить внимание на шаблон написания тестов Page Object . Этот подход позволяет выделить взаимодействие с WebDriver и повторяющиеся во время тестирования действия в изолированные классы. Практика показывает, что таким образом легче поддерживать тесты в актуальном состоянии.

Комментарии к заметке: 2

Ссылка на глобальный объект в JavaScript

Пожалуй, единственное, для чего может понадобиться глобальный объект, это экспорт каких-либо данных из модуля. Получить этот объект не составляет труда:


function myCoolFunction() {
  var global = function () { return this; }();
}

Переменная global внутри myCoolFunction() будет соответствовать window, если код выполняется в браузере, и global для окружения Node.js.

Однако, когда функция выполняется в строгом режиме , такой трюк не пройдёт. В «strict mode» значение this будет равно undefined.


function myCoolFunction() {
  'use strict';
  var global = Function('return this')();
}

Этот приём работает корректно из-за того, что функция, созданная с помощью конструктора Function, не наследует «строгость».

Оставте свой комментарий

Безопасное получение свойств вложенных объектов

Доступ к свойствам вложенных объектов через «.» или «[]» опасен тем, что если в цепочке не будет указанного объекта, то будет выброшено исключение.


var obj = {a: {b: {c: 42} } };
obj.a.x.y;

TypeError: Cannot read property ‘y’ of undefined

От таких ситуаций можно обезопаситься, если воспользоваться простой функцией.


function getProperty(obj, prop) {
  return prop.split('.')
    .reduce(function (m, i) {
      return m && typeof m == 'object' ? m[i] : null;
    }, obj);
}

var obj = {a: {b: {c: 42} } };
getProperty(obj, 'a.x.y');

null

Комментарии к заметке: 9