Тестування гачків зворотного дзвінка


34

Я розробляю плагін за допомогою TDD, і одне, що я повністю не можу перевірити, це ... гачки.

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

Я встановив тестовий пакет із WP-CLI. Відповідно до цієї відповіді , initгачок повинен спрацьовувати, але ... це не відбувається; також код працює всередині 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' ) );
  }
}

Спасибі!


Якщо ви біжите phpunit, чи можете ви побачити невдалі чи пройдені тести? Ви встановили bin/install-wp-tests.sh?
Свен

Я думаю, що частина проблеми полягає в тому, що, можливо RegisterCustomPostType::__construct(), ніколи не викликається, коли плагін завантажується для тестів. Можливо також, що на вас впливає помилка # 29827 ; можливо, спробуйте оновити свою версію модульного тестового набору WP.
JD

@Sven: так, тести не вдається; я встановив bin/install-wp-tests.sh(оскільки я використовував wp-cli) @JD: RegisterCustomPostType :: __ викликається конструкція (просто додано die()заяву і phpunit там зупиняється)
Ionut Staicu

Я не надто впевнений на стороні тестування одиниці (не моя форте), але з буквальної точки зору ви можете використовувати, did_action()щоб перевірити, чи діяли дії.
Рарст

@Rarst: дякую за пропозицію, але вона все ще не працює. Чомусь я вважаю, що терміни неправильні (тести виконуються перед initгаком).
Іонут Стайку

Відповіді:


72

Випробування ізольовано

Розробляючи плагін, найкращий спосіб перевірити його - не завантажуючи середовище WordPress.

Якщо ви пишете код, який можна легко перевірити без WordPress, ваш код стане кращим .

Кожен компонент, який перевіряється одиницею, повинен бути перевірений ізольовано : коли ви тестуєте клас, вам потрібно лише протестувати цей конкретний клас, припускаючи, що всі інші коди працюють ідеально.

Ізолятор

З цієї причини одиничні тести називаються "одиницями".

В якості додаткової переваги, не завантажуючи core, ваш тест запуститься набагато швидше.

Уникайте гачків у конструкторі

Порада, яку я можу дати вам, - це не уникати гачків у конструкторах. Це одна з речей, яка зробить ваш код перевіреним у відриві.

Давайте подивимось тестовий код в ОП:

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.

У вищевказаному класі є дві речі, які потрібно перевірити:

  • метод 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 гаками . Якщо припустити, що ви завантажили в свої тести (див. Репо 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, тому читайте репо-ремед для отримання додаткової інформації, але приклад, наведений вище, повинен бути досить зрозумілим.

Більше того, вам не потрібно писати знущань add_actionабо register_post_typeсамостійно, або підтримувати глобальні змінні.

А WP-класи?

У WP також є кілька класів, і якщо WordPress не завантажується під час запуску тестів, вам потрібно знущатися над ними.

Це набагато простіше , ніж глузливі функції, PHPUnit має вбудовану систему для фіктивних об'єктів, але тут я хочу запропонувати знущання вам. Це дуже потужна бібліотека і дуже проста у використанні. Більше того, це залежність WP_Mock, тому, якщо у вас є, у вас є і насмішки.

А як же WP_UnitTestCase?

Тестовий набір WordPress був створений для тестування ядра WordPress , і якщо ви хочете внести свій внесок у ядро, він є основним, але використання його для плагінів змушує вас протестувати не поодиноко.

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

Якщо ви сумуєте за фабриками, корисною особливістю набору, ви повинні знати, що там є дивовижні речі .

Потрапляють і недоліки

Існує випадок, коли робочого процесу, який я запропонував тут, бракує: тестування баз даних .

Насправді, якщо ви використовуєте стандартні таблиці та функції WordPress для запису туди ( $wpdbметодами найнижчого рівня ), вам ніколи не потрібно насправді записувати дані або перевіряти, чи дані насправді є в базі даних, просто переконайтеся, що належні методи викликаються належними аргументами.

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

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

(Немає потреби говорити про використання іншого db для тестів, чи не так?)

На щастя, PHPUnit дозволяє організовувати свої тести в "наборах", які можна запустити окремо, тому ви можете написати набір для тестів на замовлення баз даних, де ви завантажуєте середовище WordPress (або його частину), залишаючи всі решта ваших тестів WordPress-безкоштовно .

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

Втретє, написання коду, який легко перевіряється ізольовано, означає писати кращий код.


5
Святе лайно, багато корисної інформації! Дякую! Якось мені вдалося пропустити всю точку одиничного тестування (до цих пір я займався тестуванням PHP тільки всередині Code Dojo). Раніше сьогодні я також знаходив про wp_mock, але мені чомусь вдається проігнорувати. Що мене здивувало, це те, що будь-який тест, яким би малим він не був, для запуску знадобився щонайменше дві секунди (завантажте WP env по-перше, виконайте тест вдруге). Ще раз дякую, що відкрили мені очі!
Іонут Стайку

4
Дякую @IonutStaicu Я забув згадати, що не завантаження WordPress робить ваші тести набагато швидшими
gmazzap

6
Також варто зазначити, що тестова рамка блоку WP Core є дивовижним інструментом для запуску тестів INTEGRATION, який би був автоматизованим тестом, щоб гарантувати, що він добре інтегрується з самим WP (наприклад, немає випадкових зіткнень імені функції тощо).
Джон П Блох

1
@JohnPBloch +1 для хорошої точки. Навіть якщо використання простору імен достатньо, щоб уникнути зіткнення імен функцій у WordPress, де все глобально :) Але, звичайно, інтеграція / функціональні тести - річ. На даний момент я граю з Behat + Mink, але все ще практикуюсь з цим.
gmazzap

1
Дякую за "вертоліт" над лісом UnitTest WordPress - я все ще сміюся з цієї епічної картини ;-)
birgire
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.