Заметки с тегом «nodejs» (страница 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

Реализация WebSocket клиента для Node.js

Для нагрузочного тестирования WebSocket сервера в проекте мы решили написать ботов, имитирующих подключения пользователей, и создать с их помощью некую активность. Ботов написал я на Node.js в виде скрипта с параметрами.

Интересным моментом в этой задаче оказался выбор модуля, реализующего WebSocket подключение. Если в браузере для этого есть специальный объект, который полностью скрывает от разработчик всю кухню подключения, рукопожатия и передачи данных, то в Node.js это нужно делать, что называется, руками. Для этих целей написаны различные модули. Для моих нужд самым подходящим оказался модуль websocket.

Работа WebSocket клиента устроена аналогично net.Socket:


// Создаётся экземпляр клиента
var WebSocketClient = require('websocket').client;
var client = new WebSocketClient();

// Вешаем на него обработчик события подключения к серверу
client.on('connect', handler);

// Подключаемся к нужному ресурсу
client.connect('ws://localhost:9000/');

В обработчик подключения в качестве аргумента передаётся объект, который может принимать и отсылать сообщения:


function handler(connection) {
  connection.on('message', function (message) {
    // делаем что-нибудь с пришедшим сообщением
    console.log(message);
  }
  // посылаем сообщение серверу
  connection.sendUTF('Hi, there!');
}

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

Ограничение скорости загрузки по сети

Мне для тестирования бывает нужно ограничить скорость HTTP-трафика для проверки того, как ресурс работает в тех или иных условиях.

Для этих целей я написал на Node.js прокси-сервер, который выполняет одну простую задачу: пропускает через себя HTTP-трафик, лимитируя скорость до указанного значения.

Устанавливается он в систему с помощью npm:

npm install -g throttle-proxy

И запускается командой:

throttle-proxy

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


throttle-proxy --speed 50000 --port 9999

Остановить прокси-сервер можно комбинацией клавиш Ctrl+C в консоли, где он запущен.

Не забудьте только указать адрес 127.0.0.1 и порт 3128 (или выбранный вами порт) в настройках системы в качестве HTTP-прокси сервера.

Исходный код доступен на GitHub — throttle-proxy

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

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

Боремся с большой вложенностью анонимных колбеков

В Node.js почти все методы библиотек, работающих с внешними данными, выполняются асинхронно. Результат возвращается либо в виде событий, либо в виде вызова указанного колбека. Разработчик тут же не задумываясь использует анонимную функцию в качестве такого колбека, а затем ещё одну и ещё… В результате, достаточно сложный участок кода оказывается ещё менее читаем.

Вот несколько советов, как улучшить читаемость такого кода.

Декомпозиция

Самым очевидным решением будет использовать в качестве колбеков не анонимные функции, а именованные. Да, простая декомпозиция может значительно повысить читаемость кода.

Возможно, в именованные функции стоит вынести только бизнесс-логику, а обработку ошибок от предыдущей функции оставить в анонимном колбеке.

Step

Когда столкнулся с тем, что основная часть бизнесс-логики у меня оказывалась глубоко закопана в «ифах» и колбеках, то первой, что попалось мне на глаза, оказалась библиотека step.

Она оказалась чрезвычайно проста в использовании и дружелюбна к другим библиотекам, использующим принцип «первым аргументом колбека всегда идёт объект ошибки».


var step = require("step");

function findRecentPostsOfActiveUsers(callback) {
  step(
    // получаем список активных пользователей
    function () {
      var nextStep = this;
      User.find({active: true}, nextStep);
    },
    // получаем последнюю публикацию каждого из ранее найденных пользователей
    function (err, users) {
      if (err) throw err;
      var group = this.group();
      users.forEach(function (user) {
        Post.findOne({userId: user.id})
          .sort({date: -1})
          .exec(group());
      });
    },
    // на последнем шаге передаем найденые публикации в колбек
    // function (err, posts) { … }
    callback
  );
}

Все выбрасываемые исключения отлавливаются и передаются в качестве ошибки следующему «шагу».

Внутри каждого шага можно организовать очередь или параллельное исполнение других асинхронных функций.

Async

Библиотека async во многом аналогична step , в части последовательного выполнения указанных функций и передачи возвращаемого результата на следующий шаг. Однако, помимо этого, там реализована масса других полезных техник для управления порядком выполнения кода.

When

Отдельно хочу отметить библиотеку when, в которой реализован немного другой подход, чем в step и async. Управление последовательностью выполнения кода осуществляются на базе отложенных объектов.

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

В примерах и документации подробно описаны аспекты применения этой библиотеки.

Замечание

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

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

Тестирование promise с помощью mocha

В своём «домашнем» проекте для тестирования кода я использую фреймворк mocha . В тестах все утверждения проверяются библиотекой should.js , которая выбрасывает соответствующие исключения, отлавливаемые mocha.

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


describe("Listener", function () {
    it("should reject data", function (done) {
        var connection = new MockStream();
        var promise = listener(connection);
        connection.write("foo bar baz");
        connection.end();

        promise.then(
            function (obj) {
                done(new Error("unexpected fulfill"));
            },
            function (obj) {
                obj.should.be.a("object").and
                  .have.property("valid", false);
                done();
            }
        );

    });
});

В данном тесте я ожидаю, что promise будет отклонён с объектом, в качестве параметра, имеющим поле valid равное значению false. Если утверждение не будет соответствовать действительности, то тест не завершится естественным образом (т.е. вызов done() , который идет за утверждением, не будет выполнен).

Благо, каждый вызов метода then() возвращает другой promise-объект. Он выполняет или отклоняется так же как и его «предок», но в параметры колбеков будут передаваться совершенно другие значения. Этим можно воспользоваться для корректного завершения теста.


describe("Listener", function () {
    it("should reject data", function (done) {
        var connection = new MockStream();
        var promise = listener(connection);
        connection.write("foo bar baz");
        connection.end();

        promise.then(
            function (obj) {
                // throw new Error("unexpected fulfill");
                return new Error("unexpected fulfill");
            },
            function (obj) {
                obj.should.be.a("object").and
                  .have.property("result", false);
            }
        ).then(done, done);

    });
});

При любом изменении состояния исходного promise-объекта будет вызываться done(). Отличаться будет только параметр, с которым он будет вызван:

  • объект ошибки в случае неверного состояния или выбрасывания исключения;
  • undefined при удачном завершении теста.

Именно такие значения параметра определяют состояние теста в mocha.

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