Проверка обратных вызовов крючков

Я разрабатываю плагин с использованием TDD, и одна вещь, которую я полностью не тестирую, – это … hooks.

Я имею в виду ОК, я могу проверить обратный вызов hook, но как я могу проверить, действительно ли запущен крючок (как пользовательские, так и перехватчики по умолчанию WordPress)? Я предполагаю, что некоторые издевательства помогут, но я просто не могу понять, что мне не хватает.

Я установил набор тестов с WP-CLI. Согласно этому ответу , init hook должен срабатывать, но … это не так; Кроме того, код работает внутри WordPress.

По моему мнению, загрузочный загрузочный файл загружен последним, поэтому имеет смысл не запускать init, поэтому остается вопрос: как я должен проверить, запущены ли крючки?

Благодаря!

Файл начальной загрузки выглядит следующим образом:

 $_tests_dir = getenv('WP_TESTS_DIR'); if ( !$_tests_dir ) $_tests_dir = '/tmp/wordpress-tests-lib'; require_once $_tests_dir . '/includes/functions.php'; function _manually_load_plugin() { require dirname( __FILE__ ) . '/../includes/RegisterCustomPostType.php'; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); require $_tests_dir . '/includes/bootstrap.php'; 

проверенный файл выглядит следующим образом:

 class RegisterCustomPostType { function __construct() { add_action( 'init', array( $this, 'register_post_type' ) ); } public function register_post_type() { register_post_type( 'foo' ); } } 

И сам тест:

 class CustomPostTypes extends WP_UnitTestCase { function test_custom_post_type_creation() { $this->assertTrue( post_type_exists( 'foo' ) ); } } 

Благодаря!

Solutions Collecting From Web of "Проверка обратных вызовов крючков"

Испытание изолированно

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

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

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

Изолятор

Именно по этой причине модульные тесты называются «единицами».

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

Избегайте перехватов в конструкторе

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

Давайте посмотрим тестовый код в OP:

 class CustomPostTypes extends WP_UnitTestCase { function test_custom_post_type_creation() { $this->assertTrue( post_type_exists( 'foo' ) ); } } 

И давайте предположим, что этот тест терпит неудачу . Кто является виновником ?

  • крючок не был добавлен вообще или неправильно?
  • метод, который регистрирует тип сообщения, не был вызван вообще или с неправильными аргументами?
  • есть ошибка в WordPress?

Как это можно улучшить?

Предположим, что ваш код класса:

 class RegisterCustomPostType { function init() { add_action( 'init', array( $this, 'register_post_type' ) ); } public function register_post_type() { register_post_type( 'foo' ); } } 

(Примечание: я буду ссылаться на эту версию класса для остальной части ответа)

Способ, которым я написал этот класс, позволяет создавать экземпляры класса без вызова add_action .

В классе выше есть 2 вещи, которые нужно протестировать:

  • метод init фактически вызывает add_action передавая ему правильные аргументы
  • метод register_post_type фактически вызывает функцию register_post_type

Я не сказал, что вам нужно проверить, существует ли тип сообщения: если вы добавляете правильное действие, и если вы вызываете register_post_type , пользовательский тип сообщения должен существовать: если он не существует, это проблема WordPress.

Помните: когда вы тестируете свой плагин, вам нужно проверить свой код, а не код WordPress. В ваших тестах вы должны предположить, что WordPress (как и любая другая внешняя библиотека, которую вы используете) работает хорошо. В этом смысл модульного теста.

Но … на практике?

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

Метод «ручной»

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

Если WordPress не загружен во время выполнения тестов, это означает, что вы можете переопределить его функции, например add_action или register_post_type .

Предположим, у вас есть файл, загруженный из вашего файла начальной загрузки, где у вас есть:

 function add_action() { global $counter; if ( ! isset($counter['add_action']) ) { $counter['add_action'] = array(); } $counter['add_action'][] = func_get_args(); } function register_post_type() { global $counter; if ( ! isset($counter['register_post_type']) ) { $counter['register_post_type'] = array(); } $counter['register_post_type'][] = func_get_args(); } 

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

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

Это может быть что-то вроде:

 class Custom_TestCase extends \PHPUnit_Framework_TestCase { public function setUp() { $GLOBALS['counter'] = array(); } } 

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

И теперь ваш тестовый код (я имею в виду переписанный класс, который я написал выше):

 class CustomPostTypes extends Custom_TestCase { function test_init() { global $counter; $r = new RegisterCustomPostType; $r->init(); $this->assertSame( $counter['add_action'][0], array( 'init', array( $r, 'register_post_type' ) ) ); } function test_register_post_type() { global $counter; $r = new RegisterCustomPostType; $r->register_post_type(); $this->assertSame( $counter['register_post_type'][0], array( 'foo' ) ); } } 

Вы должны отметить:

  • Мне удалось вызвать два метода отдельно, а WordPress вообще не загружен. Таким образом, если один тест терпит неудачу, я точно знаю , кто такой преступник.
  • Как я уже сказал, здесь я тестирую, что классы вызывают функции WP с ожидаемыми аргументами. Нет необходимости проверять, действительно ли CPT существует. Если вы проверяете существование CPT, вы тестируете поведение WordPress, а не поведение плагина …

Ницца .. но это ПИТА!

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

Например, пример выше, вы можете написать класс, который регистрирует типы сообщений, вызывая register_post_type в 'init' с заданными аргументами. С этой абстракцией вам все равно нужно протестировать этот класс, но в других местах вашего кода, которые регистрируют типы сообщений, вы можете использовать этот класс, издеваясь над ним в тестах (при условии, что он работает).

Самое удивительное в том, что если вы напишете класс, который абстрагирует регистрацию CPT, вы можете создать для него отдельный репозиторий, и благодаря современным инструментам, таким как Composer, встройте его во все проекты, где вам это нужно: один раз, используйте везде . И если вы когда-либо обнаружите ошибку в ней, вы можете исправить ее в одном месте и с простым composer update будут исправлены все проекты, в которых она используется.

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

Но рано или поздно мне нужно использовать функции WP где-то …

Конечно. Вы никогда не должны действовать параллельно с ядром, это не имеет никакого смысла. Вы можете писать классы, которые обертывают функции WP, но эти классы также должны быть проверены. «Ручной» метод, описанный выше, может использоваться для очень простых задач, но когда класс содержит много функций WP, это может быть болью.

К счастью, там есть хорошие люди, которые пишут хорошие вещи. 10up , одно из крупнейших агентств WP, поддерживает очень большую библиотеку для людей, которые хотят правильно протестировать плагины. Это WP_Mock .

Это позволяет вам фальсифицировать функции WP на крючки . Предполагая, что вы загрузили в свои тесты (см. Reado readme), тот же тест, который я написал выше, становится:

 class CustomPostTypes extends Custom_TestCase { function test_init() { $r = new RegisterCustomPostType; // tests that the action was added with given arguments \WP_Mock::expectActionAdded( 'init', array( $r, 'register_post_type' ) ); $r->init(); } function test_register_post_type() { // tests that the function was called with given arguments and run once \WP_Mock::wpFunction( 'register_post_type', array( 'times' => 1, 'args' => array( 'foo' ), ) ); $r = new RegisterCustomPostType; $r->register_post_type(); } } 

Простой, не так ли? Этот ответ не является учебным WP_Mock для WP_Mock , поэтому прочитайте WP_Mock readme для получения дополнительной информации, но пример выше должен быть довольно ясным, я думаю.

Кроме того, вам не нужно писать какие-либо издеваемое add_action или register_post_type самостоятельно или поддерживать любые глобальные переменные.

И WP классы?

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

Это намного проще, чем издевательские функции, PHPUnit имеет встроенную систему для издевательства объектов, но здесь я хочу предложить вам Mockery . Это очень мощная библиотека и очень проста в использовании. Более того, это зависимость WP_Mock , поэтому, если у вас есть это, у вас тоже есть Mockery.

Но как насчет WP_UnitTestCase ?

Набор тестов WordPress был создан для тестирования ядра WordPress, и если вы хотите внести свой вклад в ядро, он имеет решающее значение, но использование его для плагинов позволяет вам тестировать не изолированно.

Положите свои глаза на мир WP: существует много современных фреймворков PHP и CMS, и ни один из них не предлагает тестировать плагин / модули / расширения (или то, что они называются) с использованием кода рамки.

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

Gotchas и минусы

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

Фактически, если вы используете стандартные таблицы и функции WordPress для записи там (при методах наименьшего уровня $wpdb ), вам никогда не нужно писать данные или проверять, действительно ли данные находятся в базе данных, просто убедитесь, что правильные методы вызывают с правильными аргументами ,

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

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

(Нет необходимости говорить, чтобы использовать другой db для тестов, не так ли?)

К счастью, PHPUnit позволяет организовать ваши тесты в «наборах», которые можно запускать отдельно, поэтому вы можете написать набор для пользовательских тестов базы данных, где вы загружаете среду WordPress (или ее часть), оставляя все остальные ваши тесты без WordPress .

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

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