我在 2019 年 WordCamp 卡塔尼亚研讨会的存储库
可选,但您可能需要安装 docker:
sudo apt-get install docker-ce docker-ce-cli containerd.io
我假设你已经安装了 Composer。我们先安装 PHPUnit:
composer require --dev phpunit/phpunit ^8.3
请同时检查要求!
PHPUnit 8.3 至少需要 PHP 7.2!顺便说一句 - PHP 7.1 的安全支持将于 2019 年 12 月 1 日结束。
提示:您没有安装Composer ?试试这个!
docker run --rm -it -v $PWD:/app -u $(id -u):$(id -g) composer install
当您计划测试 WordPress 扩展时,至少有两个有效的框架可以派上用场:
让我们试试大脑猴子:
composer require --dev brain/monkey:2.*`
这也会自动安装 Mockery 和 Patchwork。只需执行composer install
就可以了。
创建一个目录,为名为WcctaTest.php的小型测试类提供一个目录:
mkdir -p tests/wccta
出色的!现在让我们在根目录中创建一个phpunit.xml配置文件。
您还可以决定使用命令行中的配置参数来运行测试。请参阅下一部分(提示:“脚本”)!
伟大的!将一些部分添加到composer.json文件中:
composer test
让我们创建一个目录来存放我们的源代码。这是您将很快测试的第一类的地方。
mkdir -p src/wccta && touch src/wccta/Plugin.php
rm -f tests/wccta/WcctaTest.php && touch tests/wccta/PluginTest.php
touch wordpress-plugins-phpunit.php
我们想要测试Plugin
类的一些方法。想象一个名为is_loaded
的方法,成功时返回true
。准备好后,执行:
composer test
提示:您的系统或 PHP 版本不是最新的?您可以跳过这一步,但让我们尝试一些[不那么]新的东西!
docker run -it --rm -v $PWD:/app -w /app php:7.3-alpine php ./vendor/bin/phpunit
您可能会想象某些插件将有很多类,并且您很容易忘记测试所有需要测试的功能。
那么,我们来谈谈覆盖范围!
只需将自定义命令添加到composer.json中的脚本部分:
"coverage": "./vendor/bin/phpunit --coverage-html ./reports/php/coverage"
和phpunit.xml的过滤器:
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory>./src</directory>
</whitelist>
</filter>
现在只需执行composer coverage
!这将创建一个目录./reports/php/coverage
以及一些 html 文件。嗯,不是在所有计算机上。有些仍然会收到错误消息,例如:
Error: No code coverage driver is available
让我们在 docker-image 中修复这个问题。我准备了一个Dockerfile,这样你就可以执行:
docker build -t coverage .
构建过程完成后:
docker run -it --rm -v $PWD:/app -w /app coverage:latest php ./vendor/bin/phpunit --coverage-html ./reports/php/coverage
现在你知道功夫了!请在浏览器中打开文件./reports/php/coverage/index.html !
让我们将Plugin
类连接到插件。在我们真正开始测试之前,我将向您展示如何将部分代码声明为不进行测试。
@codeCoverageIgnore
这是可用的重要注释之一。我们稍后再讨论这个问题,但首先:
再次使用覆盖率报告运行单元测试!
您可能确实注意到了覆盖率报告中的CRAP
栏。 CRAP是变更风险反模式的缩写。它表明更改类或方法中的代码可能会有多大风险。您可以通过不太复杂的代码和全面的测试覆盖来降低风险(从而降低指数)。
让我们开始测试一些东西。但什么?仍然没有编写需要测试的更多功能。
TDD(测试驱动开发)登场了。
即使您决定不使用此技术,您至少应该知道我们在说什么。
我们首先创建一个 Test CarTest
来测试get_price
方法是否返回字符串'€ 14.500'
。然后创建一个Class Car
,编写满足测试的方法get_price
。不要从实施开始。
在这里我还要介绍一下TDD中广泛接受的测试模式AAA (Arrange Act Assert)。它描述了如何安排测试,与 BDD(行为驱动开发)中的GWT (Given When then)非常相似。
您可以测试您的类是否在某些条件下抛出异常。现在让我们实现get_price
方法。
只需创建一个类Registry
,将混合值设置为内部数组中的命名项。为此,请使用方法set()
或魔术方法__set()
。首先假设我们可以将 JSON 对象传递给我们的Car
类。这会给我们的班级带来更多的价值。
另一个方法get
或__get()
应该检查给定的项目是否存在并在成功时返回。如果没有这样的项目,则抛出OutOfBoundsException
。现在编写一个构造函数来处理 JSON 输入并将对象存储在 member-var data
中。 get_price
方法应该从data
变量中获取价格并处理格式化输出。
如果您在编写代码时遇到困难,请检查分支步骤 10 !变量price
应该是整数。现在这可能不是问题,因为您可以使用 PHP 函数number_format()
来创建正确的输出。但在WordPress安装中,您需要设置区域设置,例如it_IT
(意大利语)。
在WordPress中格式化数字的正确方法是使用函数number_format_i18n()
。
那么让我们改变一下,看看会发生什么:
Error: Call to undefined function wcctanumber_format_i18n()
我们稍后会解决这个问题,但让我们先做一些准备。 Brain Monkey使用PHPUnit提供的setUp()
和tearDown()
。您可以重写这些方法。让我们创建一个自定义TestCase
- 将其命名为WcctaCase
- 我们可以扩展它,因为我们可能会在每个测试类中执行此操作。
现在让我们在 autoload-dev 部分中包含测试的命名空间:
"autoload-dev": {
"psr-4": {
"tests\wccta\": "tests/wccta"
}
},
最后,让我们更改测试类的父类。
class CarTest extends WcctaTestCase { // ... }
我们准备好模拟我们的第一个WordPress函数了
Functionsexpect( $name_of_function )->andReturn( $value );
仅针对一种期望编写测试似乎需要花费太多精力。如果您想测试不同的值怎么办?
数据提供商来救援。我已经在第 5 步中谈到了注释。这个也非常有用:
@dataprovider method_that_returns_data
看看我的例子。 getData
返回一个数组的数组。每个数组都包含 3 个值。我们的test_getPrice
方法不仅可以接受带有注释的数据提供者,而且还可以将输入变量定义为参数。
您可以测试您的类是否在某些条件下抛出异常。
只需创建一个类Registry
,将混合值设置为内部数组中的命名项。为此,请使用方法set()
或魔术方法__set()
。
另一个方法get
或__get()
应该检查具有给定键的项目是否存在并在成功时返回它。如果没有这样的项目,则抛出OutOfBoundsException
。
如果您在编写代码时遇到困难,请检查分支步骤 10 !
最后一步将我们带到了工厂。什么是工厂?有时,您创建的函数或方法只是隐藏创建特定对象的复杂过程。有时您必须决定要创建哪种类型的对象。
在 WordPress 插件中,我更喜欢将工厂中的钩子添加到对象中。有一些插件可以在类构造函数中添加挂钩。这不是一件好事(特别是当您仍在测试经典方法时 - 创建一个启动并运行 WordPress 的完整环境)。
让我们创建一个带有名为create
的静态函数的类Factory
。此方法应返回一个Car
对象。但是让我们重构Car
的构造函数,使其需要一个对象而不是 JSON 字符串。我们将在Factory
类的 create 方法中执行此操作。
现在使用composer test
测试您的插件,您会看到一些错误:
TypeError: Argument 1 passed to wcctaCar::__construct() must be an object, string given, called in ...
我们也应该纠正我们的测试......
出色的!让我们为我们的工厂创建一个测试。我们暂时让该方法不包含任何内容。再次运行测试!
There was 1 risky test:
1) testswcctaFactoryTest::test_create
This test did not perform any assertions
测试通过了,但您收到消息称测试存在风险。顺便说一句:将函数命名为test_create
just create
并使用注释@test
。我相信该注释的使用取决于您的个人品味!
我们现在将更深入地探讨这一点。
创建一个接口FooterInterface
,定义一个不期望任何返回值的公共方法info
。在Car
中实现接口, info
可以 - 例如 - 输出一条有趣的消息。
为Factory
的create
方法定义返回类型FooterInterface
,并将Car
的info
方法添加到 WordPress-Action wp_footer
。
现在让我们在FactoryTest
中测试一下。至少有两种方法可以正确测试这一点。使用 has_action 或ActionsexpectAdded()
。过滤器的测试是类似的,并且在链接页面上有很好的描述。
检查composer test
是否仍然通过所有测试。
目前覆盖情况如何?执行composer coverage
并检查生成的输出。
我们的Car
类的info
方法没有被任何测试覆盖。但是我们可以测试方法的输出吗?
事实证明,使用expectOutputString 非常容易。
让我们庆祝我们学到的东西!
创建一个Locale
类,该类具有返回get_locale()
的公共方法get
。从覆盖范围中排除该方法!
现在在我们的Plugin
类中创建一个接受Locale
实例的构造函数,并将其存储在 member-var $this->locale
中。然后创建一个get_region_code
方法,该方法返回$this->locale->get()
的值。啊,删除is_loaded
方法。 ;)
在我们的测试中,我们可以创建一个Locale
类型的对象,模拟 WordPress 函数get_locale
并将其传递给Plugin
构造函数!但我想在这里使用 Mocker:
public function test_get_region_code() {
$code = 'it_IT';
$locale = Mockery::mock( Locale::class );
$locale->shouldReceive( 'get' )->andReturn( $code );
$sut = new Plugin( $locale );
$this->assertEquals( $code, $sut->get_region_code() );
}
现在您可以让您的 WordPress 插件防弹了!
玩得开心!