УрокОсновы функционального тестирования модулей на Simpletest

В этой статье я расскажу об основах тестирования в Друпале. После прочтения вы сможете написать простой тест самостоятельно. Для наглядности, по ходу статьи будет создан модуль simpletest_example, который будет предоставлять тип контента с названием simpletest_example, ничем не отличающийся от привычных нам типов story, page и т.д. Далее для этого модуля будет написан тест, проверяющий правильность его работы в различных ситуациях.

Подготовка к тестированию

Для начала убедитесь, что включен модуль simpletest. В Drupal 7 модуль simpletest является частью ядра. Его можно найти в списке модулей под названием Testing. По умолчанию он выключен, поэтому перед тестированием обязательно включите его. Чтобы убедиться в том, что simpletest работает, перейдите на страницу admin/config/development/testing/settings.

Модуль, который будет создан по ходу статьи, используется в проекте Examples под названием Simpletest Example, поэтому при необходимости все исходники можно посмотреть именно там.

Как работает Simpletest в Drupal

Основная часть функционала Друпала ориентирована на веб, поэтому важно иметь возможность протестировать его. Simpletest создаёт чистую инсталяцию Друпала и виртуальный веб браузер, после чего использует этот браузер для прогона тестируемого функционала через серию тестов точно так же, как это сделал бы любой пользователь вручную.

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

Кратное описание модуля Simpletest Example

Модуль Simpletest Example создаёт новый тип материала наподобие page или story, который содержит заголовок и содержимое ноды. По сути, больше ничего модуль не делает, однако даёт нам возможность продемонстрировать тестирование создания контента.

Для создания нового типа материала надо будет в модуль имплементировать хук hook_node_info(), и создать форму для создания нод этого типа при помощи хука hook_form(). Так же понадобятся права на создание (create simpletest_example content) и редактирование (edit own simpletest_example content) материалов. Ну и, безусловно, файл simpletest_example.info для объявления модуля.

Кстати, в этом модуле есть небольшой баг: права пользователя обрабатываются некорректно. Точнее, строка с правом редактировать собственный материал типа simpletest_example неверно обрабатывается функцией simpletest_example_access()В результате это приводит к тому, что созданный на этапе тестирования пользователь не имеет возможности изменить созданный материал. Однако к этому мы ещё вернёмся.

Что же мы будем тестировать?

Если вы уже установили модуль simpletest_example, то вы можете перейти на страницу создания материала и вживую увидеть, что должно быть протестировано. Для этого перейдите в раздел node/add/simpletest-example. Там вы увидите приблизительно следующее:

simpletest_mymodule.png

На следующем скриншоте стрелочками показано, что конкретно будет использовано при тестировании:

simpletest_mymodule_identify.png

Надеюсь, с интерфейсом Друпала вы уже знакомы, и не придётся объяснять, что будет после сохранения материала. В противном случае, вам вряд ли удастся понять дальнейшие действия.

Создание теста для модуля Simpletest Example

Вот и пришло время для создания своего теста. Для него обычно создаётся файл по принципу имя_модуля.test, который помещается в корень модуля. К слову, модуль Simpletest Example уже содержит этот файл.

При добавлении файла теста к модулю в Drupal 7, то надо будет описать его в info файле модуля в секции files[]. В нашем примере файл теста назван simpletest_example.test:

files[] = simpletest_example.test

Если вы добавляете файл с тестом к включенному модулю, то вам придётся очистить кэш Друпала перед тем, как он будет подхвачен системой. Сделать это можно на странице admin/config/development/performance кнопкой Clear all caches (очистить весь кэш).

Создание теста содержит четыре основных шага:

  • Создание структуры (просто создание класса, унаследованного от DrupalWebTestCase)
  • Инициализация теста с созданием необходимых сущностей и настройкой конфигурации
  • Создание методов тестирования модуля
  • Осознение результатов теста, чтобы понять, почему тест не работает так, как нам хотелось бы. А так же выявление ошибок в тесте (или модуле).

Для начала, нам понадобится унаследовать класс с нашим тестом от DrupalWebTestCase:

<?php
class SimpletestExampleTestCase extends DrupalWebTestCase {
  protected $privileged_user;
 
}
?>

Для того, чтобы созданный тест был виден из веб интерфейса Simpletest, необходимо в класс добавить метод getInfo(). Он предоставляет основную информацию о создаваемом тесте - имя, описание и его группу (наподобие группы у модулей, только там это package).

<?php
  public static function getInfo() {
    return array(
      'name' => 'Simpletest Example',
      'description' => 'Ensure that the simpletest_example content type provided functions properly.',
      'group' => 'Examples',
    );
  }
?>

Далее следует крайне важная функция setUp(). В ней делаются все приготовления для запуска рабочей версии инсталяции Друпала, чтобы тест в дальшейшем работал так, как нам надо. При написании кода в этой функции сперва надо задуматься - "Что мне надо создать и включить, чтобы мои тесты выполнились правильно?". В нашем случае надо сделать три вещи:

  • Включить модуль Simpletest Example
  • Создать пользователя с доступом к созданию материала типа simpletest_example
  • Авторизовать созданного пользователя

Все эти действия мы делаем в методе setUp():

<?php
  public function setUp() {
 
    // Включаем модуль Simpletest Example
    parent::setUp('simpletest_example');
 
    // Создаём пользователя с правами на создание/редактирование нод
    $this->privileged_user = $this->drupalCreateUser(array(
      'create simpletest_example content',
      'extra special edit any simpletest_example',
      ));
 
    // Авторизуем пользователя
    $this->drupalLogin($this->privileged_user);
  }
?>

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

Создание теста: Добавление материала

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

Наш первый тест будет создавать новую ноду типа simpletest_example, используя форму на странице node/add/simpletest-example:

<?php
  public function testSimpleTestExampleCreate() { 
    $edit = array();
    $edit['title'] = $this->randomName(10);
    $edit["body[und][0][value]"] = $this->randomName(50);
    $this->drupalPost('node/add/simpletest-example', $edit, t('Save'));
    $this->assertText(t('Simpletest Example Node Type @title has been created.', array('@title' => $edit['title'])));
  }
?>

drupalPost, drupalGet и методы вставок (assertations)

Выше преведённый код делает довольно простой сабмит формы со страницы node/add/simpletest-example. Сначала он подготавливает массив полей со значениями формы (заголовок и содержимое материала), а дальше просто отправляет её методом POST, указав, какая кнопка формы при этом должна быть нажата. Этот метод принципом своей работы очень похож на drupal_form_submit() (для тех, кто сталкивался).

Большинство тестов создаются по следующему шаблону:

  • При помощи drupalGet() переходят на нужную страницу или при помощи drupalPost() делают манипуляции с формами
  • Делают одну или больше вставок, чтобы сравнить то, что получилось в результате с тем, что должно было получиться

$this->drupalGet($path): просто переходит на страницу $path.

$this->drupalPost($path, $edit_fields, $submit_button_name): со страницы $path берёт форму, заполняет её поля значениями из $edit_fields и имитирует нажатие кнопки $submit_button_name.

Теперь о вставках. Существует десятки различных видов вставок. Самая простая - это $this->assertText($text_to_find_on_page). Я их все перечислять не буду - вы можете самостоятельно почитать о них.

Пользователи и среда ядра

Все тесты запускаются в отдельной среде - обычно их называют песочницами (sandbox). Поэтому авторизация пользователя методом $this->drupalLogin($account) происходит также в обособленной среде разработки. И если вы хотите вызывать функции из API ядра(например, user_access), то настоятельно рекомендуется привязывать тестового пользователя к среде ядра:

<?php
$account = $this->drupalCreateUser(array('access content'));
$this->drupalLogin($account);
global $user;
$user = user_load($account->uid);
$this->assertFalse(user_access('access content'));
?>

Simpletest переключает пользователя на пользователя с uid 1 (суперадмин) во время тестирования, и так же обеспечивает его корректное переключение обратно. Так что можно не переживать по поводу уязвимостей в безопасности сайта.

Запуск веб интерфейса Simpletest

Теперь надо запустить тест. Здесь мы и будем использовать веб интерфейс.

Перейдите на страницу с настройками тестирования /admin/config/development/testing. Здесь вы увидите два таба - List и Settings. В первой вкладке список с доступными тестами, а во второй - настройки среды тестирования.

Сейчас нас интересует первая вкладка. Выберите тест, который только что был создан (он будет находиться в группе Examples) и нажмите кнопку Run tests. Возможно, вам придётся сбросить кэш перед тем, как вы увидите ваш тест в списке.

После запуска теста вы увидите результаты прохождения тестов модулем.

Тесты также можно запускать из коммандной строки. Больше информации об этом вы найдёте здесь.

Пример теста, который не будет пройден

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

Работа по прежнему происходит внутри одного класса. Однако на этот раз мы попробуем протестировать редактирование ноды. Как я уже говорил в модуле есть баг - он некорректно обрабатывает строку с доступом пользователя на редактирование собственных материалов. Поэтому пользователь не сможет их редактировать. В этом тесте мы создадим материал и попробуем редактировать его:

<?php
  public function testSimpleTestExampleEdit() {
    // Создаём ноду
    $settings = array(
      'type' => 'simpletest_example',
      'title' => $this->randomName(32),
      'body' => array(LANGUAGE_NONE => array(array($this->randomName(64)))),
    );
    $node = $this->drupalCreateNode($settings);
 
    // Вывод структуры созданной ноды (для дебага).
    // Это сообщение покажется только в том случае, если
    // в настройках тестирования стоит галочка напротив "verbose".
    $this->verbose('Node created: ' . var_export($node, TRUE));
 
 
    // Мы будем запускать этот тест в обычном режиме, но не на testbot, т.к.
    // он должен показать, что модуль потерпел неудачу при тестировании
    if (!$this->runningOnTestbot()) {
 
      // Функция debug() будет выводить сообщение в результаты тестирования.
      // В обычной разработке её можно использовать так же, как drupal_set_message().
      debug('We are not running on the PIFR testing server, so will go ahead and catch the failure.');
 
      // Пробуем перейти на страницу редактирования материала
      $this->drupalGet("node/{$node->nid}/edit");
 
      // Убеждаемся, что мы не получили ошибку 401 - "Вы не авторизованы".
      $this->assertResponse(200, t('User is allowed to edit the content.'));
 
      // Находим в на странице в форме заголовок материала, 
      // чтобы убедиться, что форма была успешно получена.
      $this->assertText(t("@title", array('@title' => $settings['title'])), "Found title in edit form");
    }
  }
?>

Модульное тестирование

Тестирование, о котором шла речь ранее, называется функциональным. Но Simpletest предоставляет альтернативу классу DrupalWebTestCase - класс DrupalUnitTestCase. Этот вид тестирования называется модульным.

Таблицы в базе данных и файловые директории не создаются при инициализации модульного тестирования. Это позволяет ему быстрее запускаться, чем функциональному тестированию. Однако это означает, что у него нет доступа ни к базе данных, ни к файлам. Вызов любой функции Друпала, которая содержит обращение к БД приведёт к ошибке. Это относится даже к таким распространённым функциям как watchdog(), module_implements(), module_invoke_all() и т.д.

Более подробно об этом виде тестировании можно почитать в статье про модульное тестирование с Simpletest.

Отладка Simpletest'ов

В настройках тестирования (admin/config/development/testing/settings) есть опция "Provide verbose information when running tests" - включать отладочную информацию при запуске тестов. Если вы включите её, то каждое выполнение drupalGet() или drupalPost() будет сохранено в HTML файле, который будет доступен для просмотра в результатах тестирования. Это довольно полезный инструмент, которым надо уметь пользоваться.

Также вы можете воспользоваться методом $this->verbose("некое сообщение"), чтобы вывести некоторую отладочную информацию на экран при тестировании.

Больше информации о debug() и $this->verbose() вы найдёте в статье про отладку в Drupal 7.

Полезные ссылки

Комментарии

Аватар пользователя Zviryatko
Zviryatko написал:

Для метода assertText пишут не использовать format_string() вместо t()

30.04.2013 16:06
Аватар пользователя Zviryatko
Zviryatko написал:

upd: не использовать t(), а вместо нее использовать format_string()
(не проверил перед отправкой)

30.04.2013 16:09
Аватар пользователя Spleshka
Spleshka написал:

Можете пояснить почему? Я понимаю ещё $t = get_t(); Но ваше - что-то новенькое.

30.04.2013 20:15
Аватар пользователя zviryatko
zviryatko написал:

В доках пишут о не использовании t() для вывода сообщений теста, а format_string() если нужно вставить переменные. Также не советуют использовать когда не тестируешь именно работу переводов.

p.s. а отправку ответов на комментарии на почту не сложно прикрутить к блогу?

27.03.2014 12:52

Комментировать