Модульное тестирование WordPress с помощью Brain Monkey

Кто еще не знаком с тестированием и модульным тестированием можете ознакомится: Автоматизация тестированияМодульное тестирование с помощью PHPUnit.

Тестирование тем и плагинов под WordPress имеет одну большую проблему — взаимодействие с ядром. Решить ее можно с помощью библиотек Brain-WP/BrainMonkey или 10up/WP_Mock. Как писать тесты с помощью 10up/WP_Mock вы можете прочитать в статье: Модульное тестирование WordPress (PHPUnit, WP_Mock), но а сейчас разберемся с Brain Monkey.

Библиотека Brain Monkey помогает делать заглушки для функций и классов из ядра WordPress.

Установка библиотеки Brain-WP/BrainMonkey для тестирования WordPress

Устанавливаем библиотеку через composer:

composer require --dev brain/monkey

Для работы библиотеки нужно использовать фикстуры setUp и tearDown:

use BrainMonkey;
use PHPUnitFrameworkTestCase;

class Test_Main extends TestCase {
 
	public function setUp(): void {
		parent::setUp();
		MonkeysetUp();
	}
 
	public function tearDown(): void {
		MonkeytearDown();
		parent::tearDown();
	}
 
}

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

Возможности библиотеки

BrainMonkeyFunctionswhen;

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

justReturn

use function BrainMonkeyFunctionswhen;

when( 'function_name' )->justReturn( 'krya' );
$this->assertSame( 'krya', function_name() );

При помощи when мы делаем мок для функции function_name и при ее вызове будет возвращен результат из метода justReturn.

returnArg

when( 'func1' )->returnArg();
when( 'func2' )->returnArg( 2 );
when( 'func3' )->returnArg( 3 );
$this->assertSame( 'krya', func1( 'krya', 2, 3 ) );
$this->assertSame( 'krya', func2( 1, 'krya', 3 ) );
$this->assertSame( 'krya', funct3( 1, 2, 'krya' ) );

При помощи when мы делаем мок для функции function_name и при ее вызове будет возвращен аргумент функции под номером из метода returnArg.

Это очень удобно, когда нам нужно использовать функции для очистки переменных или очистки данных перед выводом(sanitize_*, esc_*).

justEcho

when( 'function_name' )->justEcho( 'krya' );
ob_start();
function_name();
$this->assertSame( 'krya', ob_get_clean() );

При вызове функции function_name выводим на экран текст из метода justEcho.

alias

when( 'duplicate' )->alias( function ( $value ) {

	return "Was " . $value . ", now is " . ( $value * 2 );
} );
$this->assertSame( 'Was 1, now is 2', duplicate( 1 ) );

Вызов функции duplicate мы заменяем на нашу функцию.

when( 'bigger' )->alias( 'strtoupper' );
$this->assertSame( 'WAS LOWER', bigger( 'was lower' ) );

При вызове функции bigger будет вызвана функция strtoupper.

BrainMonkeyFunctionsstubs;

Функция для массовой замены функций:

stubs(
	[
		'esc_attr',
		'esc_html',
		'esc_textarea',
		'__',
		'_x',
		'esc_html__',
		'esc_html_x',
		'esc_attr_x',
	]
);
$this->assertSame( 'krya', esc_attr( 'krya' ) );
$this->assertSame( 'krya', esc_html( 'krya' ) );

При вызове каждой из функций будет возвращаться первый аргумент, который был в нее передан. Тоже самое что и when( 'func1' )->returnArg(), но массово.

Так же можно вторым параметром указать результат функций:

stubs(
   [
      'is_user_logged_in',
      'current_user_can',
   ],
   true
);

Или еще круче, передав массив ключ-значени, в котором ключ — название функции, а значении примитив или callback.

stubs(
	[
		'is_user_logged_in'   => true,
		'current_user_can'    => false,
		'bigger'              => 'strtoupper',
	]
);
$this->assertTrue( is_user_logged_in() );
$this->assertFalse( current_user_can() );
$this->assertSame( 'WAS LOWER', bigger( 'was lower' ) );

Подводные камни

null нельзя передать, как результат функции, поэтому используйте __return_null для этого:

stubs(
	[
		'return_null_function' => '__return_null',
	]
);
$this->assertNull( return_null_function() );

Если вам нужно замокать функцию, которая возвращает callback. Поэтому нужно указать callback, который возвращает callback.

callback callback callback callback callback callback
stubs(
	[
		'return_callback' => function () {

			return [ 'Callback_Class', 'callback_method' ];
		},
	]
);

$this->assertSame(
	[ 'Callback_Class', 'callback_method' ],
	return_callback()
);

BrainMonkeyFunctionsexpect

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

Функция была вызвана n-раз

expect( 'once' )->once(); // 1
expect( 'twice' )->twice(); // 2
expect( 'i_dont_know' )->zeroOrMoreTimes();
expect( 'no_more_than_one' )->atLeast()->once(); // <=1
expect( 'more_than_one' )->atMost()->once(); // >= 1
expect( 'never' )->never(); // 0
expect( 'three_times' )->times( 3 ); // 3
expect( 'from_2_to_4_times' )->between( 2, 4 ); // 2-4

Функция была вызвана с такими параметрами:

expect( 'function_name' )->once()->withAnyArgs();
function_name( 1, 2, 3 );

expect( 'function_name' )->once()->withNoArgs();
function_name();

expect( 'function_name' )->once()->with( 'arg1', 'arg2' );
function_name( 'arg1', 'arg2' );

expect( 'function_name' )
	->once()
	->with( Mockery::type( 'int' ), Mockery::type( 'string' ) );
function_name( 10, 'string' );

expect( 'function_name' )->once()->with( Mockery::any() );
function_name( new stdClass() );

expect( 'function_name' )->once()->with( Mockery::anyOf( 'a', 2, true ) );
function_name( 'a' );

expect( 'function_name' )->once()->with( Mockery::not( 'a', 2, true ) );
function_name( 1 );
  • withAnyArgs() — с любыми аргументами, с любым их количеством или вовсе без них;
  • withNoArgs() — без аргументов;
  • with( ... ) — указываем точные аргументы.
  • with( Mockery::type( 'int' ), Mockery::type( 'string' ) ) — первый аргумент любое число, а второй — любая строка;
  • with( Mockery::any() ) — первый аргумент может быть абсолютно любого типа;
  • with( Mockery::anyOf( 'a', 2, true ) ) — первый аргумент любой из списка;
  • with( Mockery::not( 'a', 2, true ) ) — любой, кроме тех, что в списке.

Функция возвращает

expect( 'function_name' )->once()->andReturn( 'Baz!' );
$this->assertSame( 'Baz!', function_name() );

expect( 'function_name' )
	->twice()
	->andReturn( 'First time I run', 'Second time I run' );
$this->assertSame( 'First time I run', function_name( 'First time I run' ) );
$this->assertSame( 'Second time I run', function_name( 'Second time I run' ) );
expect( 'function_name' )
	->twice()
	->andReturnValues( [ 'First time I run', 'Second time I run' ] );
$this->assertSame( 'First time I run', function_name( 'First time I run' ) );
$this->assertSame( 'Second time I run', function_name( 'Second time I run' ) );

expect( 'function_name' )
	->once()
	->andReturnNull();
$this->assertNull( function_name() );

expect( 'function_name' )
	->once()
	->andReturnUsing( function () {

		return 'I am an alias!';
	} );
$this->assertSame( 'I am an alias!', function_name() );

expect( 'function_name' )
	->once()
	->andThrow( 'RuntimeException' );
$this->expectException( 'RuntimeException' );
function_name();
  • andReturn( 'Baz!' ) — функция вернет Baz!;
  • andReturn( 'One', 'Two' ) — функция вернет One в первый раз и Two во второй;
  • andReturnValues( 'One', 'Two' ) — функция вернет One в первый раз и Two во второй;
  • andReturnNull() — функция вернет null;
  • andReturnUsing( callback ) — функция вернет callback;
  • andThrow( 'RuntimeException' ) — функция выбросит исключение RuntimeException.

Тестирование хуков

Тестирование подключений хуков

Тестируемый класс:

class Metabox {

	public function hooks() {
		add_action( 'init', [ $this, 'init' ], 20, 4 );
		add_filter( 'the_title', [ $this, 'the_title' ], 20, 2 );
	}

	public function init() {
	}

	public function the_title() {
	}

}

Проверить подключение хуков можно с помощью функций has_action и has_filter:

public function test_hooks() {
	$metabox = new Metabox();
	$metabox->hooks();

	$this->assertTrue( has_action( 'init', [ $metabox, 'init' ] ) );
	$this->assertTrue( has_filter( 'the_title', [ $metabox, 'the_title' ] ) );
}

Тестирование наличия хуков

Пример класса:

class Metabox {

	public function awesome_method() {
		do_action( 'my_action', $this );

		return apply_filters( 'my_filter', 'Filter applied', $this );
	}

}

Проверяем с помощью did_action и applied, которые возвращают кол-во вызовов данных хуков:

public function test_awesome_method() {
	$metabox = new Metabox();

	$this->assertNull( $metabox->awesome_method() );
	$this->assertSame( 1, did_action( 'my_action' ) );
	$this->assertSame( 1, applied( 'my_filter' ) );
}

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

Поэтому тестировать лучше и более качественее с помощью BrainMonkeyActionsexpectDone и BrainMonkeyFiltersexpectApplied:

public function test_awesome_method() {
	$metabox = new Metabox();
	expectDone( 'my_action' )
		->once()
		->with( $metabox );
	expectApplied( 'my_filter' )
		->once()
		->with( 'Filter applied', $metabox )
		->andReturn( 'Brain Monkey rocks!' );

	$metabox->awesome_method();
}

Вывод

С помощью Brain Monkey мы можем тестировать большую часть функциональности WordPress. А как вы считаете что лучше WP_Mock или Brain Monkey?

Добавить комментарий

%d такие блоггеры, как: