PHPUnit тесты кода со встроенными функциями PHP

В процессе PHPUnit-тестирования зачастую приходится иметь дело с кодом, который зависит от встроенных функций PHP, например, phpversion(). Методика тестирования изложена в настоящей статье.

Рассмотрим в качестве простого примера следующий класс, который проверяет текущую версию PHP и сообщает, удовлетворяет ли она требованиям.

<?php
/**
 * Class to check requirements of the plugin.
 *
 * @package sample-plugin
 */

/**
 * Class Sample_Requirements
 */
class Sample_Requirements {

	/**
	 * Check php version.
	 *
	 * @return bool
	 */
	public function is_php_version_required() {
		if ( version_compare( '5.6', phpversion(), '>' ) ) {
			$this->php_requirement_message();

			return false;
		}

		return true;
	}

	/**
	 * Show notice with php requirement.
	 */
	public function php_requirement_message() {
		// Some code to show the message.
	}
}

Как протестировать метод is_php_version_required()? Можно, конечно, написать вспомогательный метод, например:

	public function phpversion() {
		return phpversion();
	}

и обращаться к нему: $this->phpversion(). А в тестах моделировать этот метод. Но выглядит это тяжеловесно.

Есть способ подмены встроенных функций PHP. Предоставляет его библиотека lucatume/function-mocker. Она использует библиотеку antecedent/patchwork, которая и делает основную работу через monkey patch.

В bootstrap.php файл надо добавить use и инициализацию FunctionMocker:

use tadFunctionMockerFunctionMocker;

// Main bootrstrap code...

FunctionMocker::init(
	[
		'whitelist'             => [
			realpath( PLUGIN_PATH . '/includes' ),
		],
		'blacklist'             => [
			realpath( PLUGIN_PATH ),
		],
		'redefinable-internals' => [ 'phpversion' ],
	]
);

Здесь whitelist — путь к папке, где расположены тестируемые классы плагина. В этой папке patchwork будет подменять функции. blacklist — путь к корневой папке плагина, здесь замен не будет (кроме папки whitelist). Это важно, потому что некоторые библиотеки (например, тестовая Mockery) иначе не работают. redefinable-internals — перечень функций, которые будут изменены при тестировании.

Теперь результат работы встроенной функции можно подменять «на лету», прямо во время выполнения теста:

<?php
/**
 * Test_Sample_Requirements class file
 *
 * @package sample-plugin
 */

use PHPUnitFrameworkTestCase;
use tadFunctionMockerFunctionMocker;

/**
 * Class Test_Sample_Requirements
 *
 * @group requirements
 */
class Test_Sample_Requirements extends TestCase {

	/**
	 * Test if is_php_version_required().
	 */
	public function test_is_php_version_required() {
		$subject = new Sample_Requirements();

		FunctionMocker::replace( 'phpversion', '5.5' );
		$this->assertFalse( $subject->is_php_version_required() );

		FunctionMocker::replace( 'phpversion', '5.6' );
		$this->assertTrue( $subject->is_php_version_required() );
	}
}

Кроме того, FunctionMocker предоставляет много дополнительных возможностей: замену статических методов, «подглядывание» за методами, замену глобальных переменных и методов глобальных объектов ( например, $wpdb->get_row ) и т.д.

Источник: KAGG Design

Игорь Гергель

Единственный обладатель значков золотой WordPress и бронзовый WooCommerce на StackOverflow RU. WordPress Core contributor. Работал ведущим девелопером в команде WPML.

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