カーネルテストとは
DrupalのPHPUnitのテストには主にユニットテスト、カーネルテスト、ファンクショナルテストというものがあります。
ユニットテストはDrupalのブートストラップ無し状態で行うテストです。一番高速ですがDrupalのサービスクラスなどを呼び出す際にいわゆる「モック」を作らなくてはいけないというデメリットがあります。またユニットテストでDBに接続するのは(頑張ればできるけど)非推奨とされています。
ファンクショナルテストはいわゆるブラウザテストで、新しくインストールされたDrupalに対して仮想ブラウザからリクエストを送る形でテストを行います。DrupalのコアとDBが使えたりユーザー目線のテストができるという反面実行速度が遅いというデメリットがあります。
カーネルテストはユニットテストとファンクショナルテストの中間に位置するもので、Drupalカーネルのブートストラップがされている状態なのでDrupalの各種サービスやDBは使えるけど、仮想ブラウザからのアクセスは行えないというものです。ユニットテストのようにサービスを使うたびにモックを書かなくてもよく、ファンクショナルテストより速いというメリットがあります。リクエストを行わないようなバックエンドの処理のテストに最適なのはもちろんですが、こちらのドキュメントを読む限りHTTPリクエストも作れるので、どうしてもブラウザからアクセスしないとダメっていう場合(UIのテスト等?)以外のファンクショナルテストはカーネルテストに落とし込むことができるのではないかなと思います。
ここらへんの話はこちらの記事が詳しく参考になりました
基本のカーネルテスト
今回はこんなサービスとテストを作成してみます。
- 「[現在のユーザーのユーザー名]さん、Hello World!」という文字列を取得するだけのパブリックメソッドを持つサービスを作成する
- 上記のメソッドがちゃんと「[現在のユーザーのユーザー名]さん、Hello World!」という文字列を返すかをテストする
(カーネルテストの特徴として「DBや構成にアクセスできる」というものがあるので、現在のユーザーのユーザー名にアクセスするようにしてみました)
テスト環境の用意
DrupalでPHPUnitのテストをする場合はまず以下のようにphpunit.xml
を編集する必要があります。
-
app/core/phpunit.xml.dist
をコピーしてapp/core/phpunit.xml
にリネーム -
phpunit.xml
を以下のように編集-
<env name="SIMPLETEST_BASE_URL" value=""/>
のvalue
にサイトのベースURLを入力(例:http://localhost:8080
) -
<env name="SIMPLETEST_DB" value=""/>
のvalue
にデータベースURLを入力(例:mysql://username@localhost/my_database
)
-
- この時点で
core
ディレクトリに移動し../../vendor/bin/phpunit [任意のディレクトリ]
を入力し、そのでディレクトリ下のテストが実行されればOK(特にコアやコントリビュートモジュールの/test/src/Unit
(ユニットテストのディレクトリ)が早く実行できるのでオススメ)
../../vendor/bin/phpunit modules/datetime/tests/src/Unit
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.
Testing Drupal\Tests\datetime\Unit\Plugin\migrate\field\DateFieldTest
...... 6 / 6 (100%)
Time: 191 ms, Memory: 6.00 MB
サービスの作成
以下のようにサービスファイルとservices.yml
を作成してカスタムモジュールに配置します。
my_module/src/Services/MyModuleService.php
<?php
namespace Drupal\my_module\Services;
use Drupal\Core\Session\AccountInterface;
/**
* MyModuleのサービス.
*/
class MyModuleService {
/**
* コンストラクタ.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* 現在のユーザー.
*/
public function __construct(AccountInterface $current_user) {
$this->currentUser = $current_user;
}
/**
* 「現在のユーザーのユーザー名]さん、Hello World!」という文字列を返却する.
*
* @return string
* 「現在のユーザーのユーザー名]さん、Hello World!」という文字列.
*/
public function getHelloWorld() {
$name = $this->currentUser->getAccountName();
return $name . 'さん、Hello World!';
}
}
my_module/my_module.services.yml
サービス名はmy_module_service
としました。サービスクラスのコンストラクタで使えるようにcurrent_user
サービスを注入しています。
services:
my_module_service:
class: Drupal\my_module\Services\MyModuleService
arguments: ['@current_user']
この時点でdrush php
でサービスが使えるか軽く確認します。you have requested a non-existent service
等のエラー等が出る場合はymlやサービスクラスの内容が合ってるかもう一度確認してみてください。
>>> \Drupal::service('my_module_service')->getHelloWorld();
=> "さん、Hello World!"
(drush php
のときは匿名ユーザーなので名前は空)
テストの作成
上記のサービスに対するテストを作成します。
my_module/tests/src/Kernel/MyModuleTest.php
<?php
namespace Drupal\my_module\tests\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* MyModuleサービスのテスト.
*/
class MyModuleTest extends KernelTestBase {
use \Drupal\Tests\user\Traits\UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'user',
'my_module',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->currentUser = $this->setUpCurrentUser();
$this->myService = \Drupal::service('my_module_service');
}
/**
* HelloWorldを取得できるかをテストする.
*/
public function testHelloWorld() {
$hello_world = $this->myService->getHelloWorld();
$name = $this->currentUser->getAccountName();
$this->assertEqual($hello_world, $name . 'さん、Hello World!');
}
}
ポイントは以下です。
- ファイル名は
XXTest.php
にする必要があります。(ディレクトリは適当でもいいっぽい?) -
\Drupal::currentUser()
で現在のユーザーを取得することもできますが、匿名(ユーザー名無し)になってしまうので、こちらのドキュメントの方法で認証済みユーザー(ユーザー名有り)を現在のユーザーとして取得しました。 -
$modules
にuser
を入れないとCall to undefined function Drupal\Tests\user\Traits\user_password()
というエラーになるので入れました。 - テストケースは
testXX
という名前のパブリックメソッドにする必要があります。今回はtestHelloWorld()
としました。 -
assertEqual
などカーネルテストで使用できるメソッドはこちらのドキュメントをご覧ください。
テストの実行
テストコードが出来たら、さきほどのように../../vendor/bin/phpunit ../modules/custom/my_module
を実行し、以下のように表示されれば成功です。
Testing ../modules/custom/my_module
. 1 / 1 (100%)
Time: 709 ms, Memory: 6.00 MB
以下のようにテスト中にアウトプットを行いたい場合は、
public function testHelloWorld() {
$hello_world = $this->myService->getHelloWorld();
$name = $this->currentUser->getAccountName();
var_dump($name);
$this->assertEqual($hello_world, $name . 'さん、Hello World!');
}
phpunit.xml
のbeStrictAboutOutputDuringTests
をfalse
にし、コマンドに--debug
オプションを付けると以下のようにアウトプットを取得できます。(beStrictAboutOutputDuringTests
がtrueのままでも出力できますが、エラーメッセージが表示され見づらくなります)
Testing ../modules/custom/my_module
Test 'Drupal\my_module\test\Kernel\MyModuleTest::testHelloWorld' started
Test 'Drupal\my_module\test\Kernel\MyModuleTest::testHelloWorld' ended
rbfw3FyF <- $nameの値
Time: 676 ms, Memory: 6.00 MB
というわけで基本のカーネルテストの作り方でした。