WordCamp Catania 2019 でのワークショップのリポジトリ
オプションですが、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 拡張機能をテストする場合に便利な有効なフレームワークが少なくとも 2 つあります。
ブレインモンキーを試してみましょう:
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
クラスのいくつかのメソッドをテストしたいと思います。成功するとtrue
を返すis_loaded
というメソッドを想像してください。準備ができたら、以下を実行します。
composer test
ヒント: システムまたは PHP のバージョンが最新ではありませんか?このステップをスキップすることもできますが、[それほど] 新しいことを試してみましょう。
docker run -it --rm -v $PWD:/app -w /app php:7.3-alpine php ./vendor/bin/phpunit
おそらく、一部のプラグインには多数のクラスがあり、テストが必要なすべての機能のテストを忘れてしまう可能性があることが想像できます。
それでは、カバレッジについて話しましょう。
カスタムコマンドをcomposer.jsonのscriptsセクションに追加するだけです。
"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 イメージで修正しましょう。すぐに実行できるように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
これは、利用可能な重要な注釈の 1 つです。これについては後ほど説明しますが、まず次のことを行います。
カバレッジレポートを使用して単体テストを再度実行してください。
カバレッジレポートのCRAP
列に気づいたかもしれません。 CRAP は、 Change Risk Anti-Patternの頭字語です。これは、クラスまたはメソッド内のコードの変更がどれほど危険であるかを示します。コードの複雑さを減らし、テストを完全にカバーすることで、リスク (したがってインデックス) を下げることができます。
何かをテストしてみましょう。でも何?テストが必要な機能はまだ書かれていません。
ここで TDD (テスト駆動開発) が登場します。
このテクニックを使用しないことに決めたとしても、少なくとも私たちが何を言っているのかを知っておく必要があります。
まず、 get_price
メソッドが文字列'€ 14.500'
を返すかどうかをテストする Test CarTest
を作成しましょう。次に、クラス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 );
たった 1 つの期待値に対してテストを作成するのは、労力が多すぎるように思えます。異なる値に対してテストしたい場合はどうすればよいでしょうか?
データプロバイダーが助けてくれます。アノテーションについてはステップ 5 ですでに説明しました。これも非常に便利です。
@dataprovider method_that_returns_data
私の例を見てください。 getData
配列の配列を返します。これらの配列にはそれぞれ 3 つの値が含まれています。 test_getPrice
メソッドは、注釈付きのデータプロバイダーを受け入れるだけでなく、入力変数をパラメーターとして定義することもできます。
クラスが特定の条件で例外をスローするかどうかをテストできます。
内部配列内の名前付き項目として混合値を設定するクラスRegistry
を作成するだけです。これには、メソッドset()
またはマジック メソッド__set()
を使用します。
別のメソッドget
または__get()
指定されたキーを持つ項目が存在するかどうかを確認し、成功した場合はそれを返す必要があります。そのような項目がない場合は、 OutOfBoundsException
をスローします。
コードを書くのが難しい場合は、分岐ステップ 10を確認してください。
最後のステップでは、 Factoriesに到達しました。工場とは何ですか?場合によっては、特定のオブジェクトを作成するための複雑なプロセスを単に非表示にする関数やメソッドを作成することがあります。また、どのタイプのオブジェクトを作成するかを決定する必要がある場合もあります。
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
という名前を付け、 create
名前を付けて、アノテーション@test
を使用します。その注釈の使用はあなたの個人的な好みに依存すると思います。
これについてもう少し詳しく見ていきます。
戻り値を期待しないパブリック メソッドinfo
を定義するインターフェイスFooterInterface
を作成します。 Car
にインターフェイスを実装すると、 info
、たとえば、面白いメッセージを出力できます。
Factory
のcreate
メソッドの戻り値の型FooterInterface
を定義し、 Car
のinfo
メソッドを WordPress-Action wp_footer
に追加します。
次に、これをFactoryTest
でテストしてみましょう。これを適切にテストするには少なくとも 2 つの方法があります。 has_action またはActionsexpectAdded()
を使用します。フィルタのテストも同様であり、リンク先のページで詳しく説明されています。
composer test
すべてのテストに合格するかどうかを確認します。
現在の報道状況はどうですか? composer coverage
を実行し、生成された出力を確認します。
Car
クラスのinfo
メソッドはテストの対象になっていません。しかし、メソッドの出力をテストできるでしょうか?
ExpectOutputString を使用すると、非常に簡単であることがわかります。
学んだことを祝いましょう!
get_locale()
を返すパブリック メソッドget
を持つクラスLocale
を作成します。その方法を対象から除外してください!
次に、 Locale
インスタンスを受け入れるコンストラクターをPlugin
クラスに作成し、それを member-var $this->locale
に保存します。次に、 $this->locale->get()
の値を返すメソッドget_region_code
を作成します。ああ、 is_loaded
-method も削除してください。 ;)
私たちのテストでは、 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 プラグインを完璧なものにすることができます。
楽しむ!