我的意思是,我可以测试挂钩回调,但是如何测试挂钩是否真正触发(自定义钩子和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' ) );
}
}
谢谢!
/>
#1 楼
隔离测试开发插件时,最好的测试方法是不加载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
即可创建该类的实例。在上面的类中,有两件事需要测试:
方法
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行为,而不是插件行为...
很好..但是它是PITA!
是的,如果您必须手动操作模拟所有的WordPress函数,这确实很痛苦。
我能提供的一些一般性建议是使用尽可能少的WP函数:您不必重写WordPress,但可以在自定义类中使用抽象的WP函数,以便可以对其进行嘲笑并轻松对其进行测试。
例如关于上面的示例,您可以编写一个注册帖子类型的类,并使用给定的参数在'init'上调用
register_post_type
。有了这种抽象,您仍然需要测试该类,但是在代码中注册帖子类型的其他地方,您可以使用该类,在测试中对其进行模拟(因此假设它可以工作)。
棒的是,如果您编写了一个抽象CPT注册的类,则可以为其创建一个单独的存储库,这要归功于像Composer这样的现代工具将其嵌入到需要的所有项目中:测试一次,在任何地方使用。而且,如果您发现其中的错误,则可以将其修复在一个位置,并使用简单的
composer update
修复所有使用它的项目。第二次:编写可独立测试的代码意味着编写更好的代码。
但是迟早我需要在某个地方使用WP函数...
当然。您永远不应与核心并行,这没有任何意义。您可以编写包装WP函数的类,但是这些类也需要进行测试。上面描述的“手动”方法可以用于非常简单的任务,但是当一个类包含很多WP函数时,可能会很痛苦。
幸运的是,那里有很多好人在写好东西。最大的WP机构之一10up,为希望以正确方式测试插件的人们提供了一个非常不错的库。它是
WP_Mock
。它允许您模拟WP函数的钩子。假设您已经加载了测试(请参见repo自述文件),则与我上面编写的测试相同:
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拥有一个嵌入式系统来模拟对象,但是在这里我想向您推荐Mockery。这是一个非常强大的库,非常易于使用。而且,它是
WP_Mock
的依赖项,因此,如果您也有Mockery。WP_UnitTestCase
呢?创建WordPress测试套件是为了测试WordPress核心,如果您想为核心做贡献它至关重要,但是将其用于插件只会使您无法孤立地进行测试。
将目光投向WP world:那里有很多现代的PHP框架和CMS,但它们都没有建议测试插件/模块/
如果错过了工厂,这是套件的一项有用功能,那么您必须知道那里有很棒的东西。
陷阱和缺点
在某些情况下,我在这里建议的工作流程会缺少:自定义数据库测试。
实际上,如果您使用标准的WordPress表和函数在此处编写(最低级别的
$wpdb
方法),则实际上不需要写数据或测试数据是否确实在数据库中,只需确保使用正确的argume调用正确的方法nts。但是,您可以编写带有自定义表和函数的插件来构建要写入的查询,并测试这些查询是否有效是您的责任。
在这种情况下,WordPress测试套件可以为您提供很多帮助,在某些情况下,可能需要加载WordPress才能运行
dbDelta
之类的功能。(不必说使用其他数据库进行测试,不是吗?)
幸运的是,PHPUnit允许您组织您的测试可以在“套件”中单独运行,因此您可以编写一个用于自定义数据库测试的套件,在该套件中加载WordPress环境(或其中的一部分),而其余所有测试都无需WordPress。
只有确保编写的类可以抽象出尽可能多的数据库操作,所有其他插件类都应使用它们,以便使用模拟功能,您可以正确地测试大多数类,而无需处理数据库。
第三次,编写易于隔离测试的代码意味着编写更好的代码。
评论
废话,很多有用的信息!谢谢!我以某种方式设法错过了单元测试的整个要点(直到现在,我只在Code Dojo内练习PHP测试)。我今天早些时候也发现了关于wp_mock的信息,但是由于某种原因,我设法忽略了它。令我感到恼火的是,无论测试多么小,它至少要花两秒钟才能运行(首先加载WP env,然后执行测试)。再次感谢您睁开眼睛!
–爱奥努特(Ionut Staicu)
2014年10月11日15:08
谢谢@IonutStaicu我忘了提一下,不加载WordPress可以使您的测试更快
– gmazzap♦
2014年10月11日15:13
还值得指出的是WP Core单元测试框架是运行INTEGRATION测试的绝佳工具,它将是自动化测试,以确保它与WP本身很好地集成(例如,不会发生偶然的功能名称冲突等)。
– John P Bloch
2014年10月11日19:15
@JohnPBloch +1为好点。即使使用名称空间足以避免WordPress中任何函数名称冲突,在WordPress中,函数都是全局的:)但是,可以肯定的是,集成/功能测试是一回事。我目前正在与Behat + Mink一起玩,但我仍在练习。
– gmazzap♦
2014年10月11日20:17
感谢WordPress的UnitTest森林上的“直升机之旅”-我仍在笑那史诗般的照片;-)
– birgire
14-10-12在10:33
评论
如果您正在运行phpunit,可以看到失败或通过的测试吗?您安装了bin / install-wp-tests.sh吗?我认为问题的一部分是在为测试加载插件时可能永远不会调用RegisterCustomPostType :: __ construct()。您还可能会受到错误#29827的影响;也许尝试更新您的WP单元测试套件的版本。
@Sven:是的,测试失败了;我安装了bin / install-wp-tests.sh(因为我使用过wp-cli)@ J.D .:调用RegisterCustomPostType :: __ construct(只是添加了die()语句并且phpunit在那里停止)
我不太确定在单元测试方面(不是我的强项),但是从字面上看,您可以使用did_action()来检查是否触发了动作。
@Rarst:感谢您的建议,但仍然无法正常工作。出于某种原因,我认为计时不正确(测试是在进行初始化钩子之前运行的)。