Unit-тестирование Drupal

Аватар пользователя artur.baranok
Опубликовано вт, 07/14/2015 - 14:20 пользователем artur.baranok
Форумы: 

Зачем нам тестирование?

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

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

Что же нам предлагает для тестирования Drupal?

Тестирование модулей и функциональности в Drupal осуществляется с помощью модуля SimpleTest. Причем, с 7 версии она включена в ядро, поэтому смотреть в другую сторону особого смысла и нет.

Установка
Для установки Вам потребуется установленный Drupal и чтобы на сервере была доступна библиотека php-curl, с помощью которой модулем осуществляется парсинг страниц.

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

patch -p0 < {путь-к-папке-с-модулями}/simpletest/D6-core-simpletest.patch

После этого достаточно активировать его во вкладке с модулями и можно просмотреть список доступных тестов на странице admin/build/testing.

Как работает SimpleTest?

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

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

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

Кстати, для каждой функции testXXX setUp выполняется каждый раз, перед выполнением теста.

Первый тест
Итак, закончим флуд и перейдем к практике. В первом тесте мы проверим создание материала типа Page, который доступен во всех установках. Для этого нам потребуется:

Создать файл теста с именем имя_модуля.test и сохранить его в папке с модулем. Имя файла жестко оговорено в SimpleTest.

Далее создаем сам тест:

<?php

class OurModuleTest extends DrupalWebTestCase {

// вспомогательная функция, которой мы будем генерировать текст с блекджеком и пробелами

protected function randomText($wordCount = 32) {

$text = '';

for ($i = 0; $i < $wordCount; $i++) {

$text .= $this->randomString(rand(4, 12)) . ' ';

}
return $text;
}

// Информация о тесте, которая отображается на странице тестов.

public static function getInfo() {
return array(
'name' => 'Page creation test',
'desc' => 'Testing page creation',
'group' => 'Our tests',
);
}

public function setUp() {

// устанавливаем необходимые модули

$args = func_get_args();

$modules = array_merge(array('help', 'search', 'menu', 'node'), $args);

call_user_func_array(array('parent','setUp'), $modules);

// устанавливаем необходимые права доступа

$permissions = array('access content', 'create page content', 'delete own page content', 'edit own page content');

// создаем пользователя с этими правами и входим в систему

$user = $this->drupalCreateUser($permissions);

$this->drupalLogin($user);

}

// Тестирование создания страницы

public function testPageCreation() {

$params = array(

'title' => $this->randomName(32),

'body' => $this->randomText(),

);

// Вызываем страницу создания Page

$this->drupalPost('node/add/page', $params, t('Save'));

// Проверяем полученный ввод

$this->assertText(t('Page @name has been created.', array('@name' => $params['title'])), t('Page creation'));

}

}

?>

Очищаем кеш и идем на страницу admin/build/testing. Теперь там мы наблюдаем раскрывающуюся вкладку "Our tests", в которой доступен один тест "Page creation test". Поставив на нем галочку выполняем его. после выполнения нам доступна информация "19 passes, 0 fails, and 0 exceptions". То, что мы и хотели получить.

Теперь разлогиним пользователя и попробуем после этого выполнить тест. Для этого создадим еще один тест и назовем его testAnonymousPageCreation. От предыдущего теста код будет отличаться только тем, что перед выполнением мы выполним $this->drupalLogout()

// Тестирование создания страницы анонимным пользователем

public function testAnonymousPageCreation() {

// Разлогиниваем пользователя

$this->drupalLogout();

$params = array(

'title' => $this->randomName(32),

'body' => $this->randomText(),

);

// Вызываем страницу создания Page

$this->drupalPost('node/add/page', $params, t('Save'));

// Проверяем полученный ввод

$this->assertText(t('Page @name has been created.', array('@name' => $params['title'])), t('Page creation'));

}

Теперь результат выполнения 29 passes, 5 fails, and 0 exceptions. Однако

это далеко не тот результат, который стоило получать. В данном случае

нужно проверить заблокирован ли доступ пользователю к этой странице, эт

и будет успешным тестом, для этого модифицируем тест:

// Тестирование создания страницы анонимным пользователем

public function testAnonymousPageCreation() {

// Разлогиниваем пользователя

$this->drupalLogout();

// Пытаемся получить необходимую страницу

$this->drupalGet('node/add/page');

// Проверяем ответ сервера на ошибку 403 (Access denied)

$this->assertResponse(403, t('You have no permitions to this page.'));
}

Теперь результат: 30 passes, 0 fails, and 0 exceptions. Отлично, теперь мы точно знаем, что неавторизированный пользователь не может получить доступа к созданию страниц.

Что дальше?

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

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

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

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

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

Маленький бонус

Есть набор мелких и серьезных проблем и вопросов, связанных с тестированием с помощью этого модуля, на которые Вы или натолкнетесь, или нет, но мы Вас предупредили улыбка

SimpleTest не может тестировать JavaScript, поэтому функционал jQuery, динамические подмены контента и т.п. тестировать не получится грустный

Список доступных проверок (Assertions) доступен тут: http://drupal.org/node/265828

Для форм модуля View нужно вызывать $this->drupalGet(), вместо drupalPost(). Пример:

$params = array('sorting' => 'sorting_value');

$this->drupalGet('find/wine-ratings', array('query' => $params));

Тесты доступны и для неактивных модулей.

Создание типов и т.п. стоит выносить в отдельный модуль, и прописывать необходимые процедуры в module_name.install.

Если создается отдельный модуль специально для тестирования, то в файле module_name.info стоит добавить hidden = TRUE, после этого модуль может вызываться в тестах, но не будет доступен в общем списке.

Модуль nodecomment конфликтует с модулем comment, поэтому стоит отредактировать файл profilesdefaultdefault.profile и удалить его из установки по-умолчанию.

Ну и напоследок расширенный вариант класса DrupalWebTestCase, в который добавлен набор дополнительных функций и свойств:

class ExtendedDrupalWebTestCase extends DrupalWebTestCase{
protected $admin_user;
protected $users;

// вспомогательная функция, которой мы будем генерировать текст с блекджеком и пробелами

protected function randomText($wordCount = 32) {
$text = '';
for ($i = 0; $i < $wordCount; $i++) {
$text .= $this->randomString(rand(4, 12)) . ' ';
}
return $text;
}

// Смена текущей темы

protected function setTheme($new_theme) {
db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' and name = '%s'", $new_theme);
variable_set('theme_default', $new_theme);
drupal_rebuild_theme_registry();

}

// генерация имени файла для вывода, в папку, которая находится вне временных папок SimpleTest и позволяет просматривать данные после очистки.

protected function getOutputFile() {
$file_dir = file_directory_path();
$file_dir .= './simpletest_output_pages';
if (!is_dir($file_dir)) {
mkdir($file_dir, 0777, TRUE);
}
return "$file_dir/$basename." . $this->randomName(10) . '.html';
}

// Запись страницы

protected function outputAdminPage($description, $basename, $url) {
$output_path = $this->getOutputFile();
$this->drupalGet($url);
$rv = file_put_contents($output_path, $this->drupalGetContent());
$this->pass("$description: Contents of result page are ".l('here', $output_path));
}

// Запись последнего экранного вывода

protected function outputScreenContents($description, $basename) {
$output_path = $this->getOutputFile();
$rv = file_put_contents($output_path, $this->drupalGetContent());
$this->pass("$description: Contents of result page are ".l('here', $output_path));
}

// Запись переменной в файл

protected function outputVariable($description, $variable) {
$output_path = $this->getOutputFile();
$rv = file_put_contents($output_path, '<html><body><pre>'.print_r($variable, true).'</pre></body></html>');
$this->pass("$description: Contents of result page are ".l('here', $output_p
}
}