PHP_CodeSnifferのカスタムルールのテストを書くよ
はじめに
PHP_CodeSnifferのカスタムルールの作り方は公式チュートリアルに書いてあるけど、テストコードに関する情報がないので実際に作った内容を記事に書いておこうと思います。
ちなみに、PHP_CodeSnifferには元々PHPUnitでテストを書く仕組みは用意されています。
※PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTestなど
ただ、composer.jsonを見る限り、対応しているPHPUnitのバージョンも古いので今回は独自にテストを作成したいと思います。
環境
- PHP 8.2.7
- PHP_CodeSniffer 3.7.2
- PHPUnit 10.2.2
今回は以前作ったカスタムルールの変数名はスネークケース以外はダメってルールのテストを作成しようと思います。
テストクラスの作成
tests
ディレクトリ配下の任意のところに作ってもらえれば問題ないと思いますが、
今回はSniffファイルと同じディレクトリ構成にしておこうと思います。
src/PHPCSCustomRules/Sniffs/NamingConventions/SnakeCaseVariableNameSniff.php
↓
tests/PHPCSCustomRules/Sniffs/NamingConventions/SnakeCaseVariableNameSniffTest.php
テストケースの作成
テストケースに必要な内容としては、次の通りです。
- テストファイルの作成
-
PHP_CodeSniffer\Config
のインスタンス作成 -
PHP_CodeSniffer\Ruleset
のインスタンス作成 -
PHP_CodeSniffer\Files\LocalFile
のインスタンス作成 - Sniffファイル実行結果の検証
- すべてを組み合わせる
1. テストファイルの作成
テストではPHP_CodeSnifferを動かして、エラーが起きる、起きないの検証を行うので、適当な内容のphpファイルを用意します。
作成する場所はテストクラスと同じ場所にしておくと、どのテストのテストファイルなのかわかりやすくて良いかと思います。
tests/PHPCSCustomRules/Sniffs/NamingConventions/fixture.php
今回は変数名が、スネークケースなのかを検証するルールのテストなので、下記のような内容にしておきます。
<?php
$snake_case = 1;
$camelCase = 2;
class A
{
public int $property_name = 1;
public int $propertyName = 2;
public function methodA(int $variable_name)
{
$snake_case = 1;
$camelCase = 2;
}
public function methodB(int $variableName)
{
$snake_case = 1;
$camelCase = 2;
}
}
PHP_CodeSniffer\Config
のインスタンス
2. PHP_CodeSniffer\Config
はインスタンスを生成するだけです。
use PHP_CodeSniffer\Config;
$config = new Config();
PHP_CodeSniffer\Ruleset
のインスタンス
3. PHP_CodeSniffer\Ruleset
は実行したいSniffファイルをロードするためのクラスです。
インスタンスを生成するには、PHP_CodeSniffer\Config
のインスタンスが必要になります。
また、registerSniffs
メソッドを使ってテストで実行したいSniffファイルの読み込みを行います。
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Ruleset;
$config = new Config();
$ruleset = new Ruleset($config);
// 指定するときは必ず配列で指定してください。
$sniffFiles = [__DIR__ . '/../../../../src/PHPCSCustomRules/Sniffs/NamingConventions/SnakeCaseVariableNameSniff.php'];
// 2,3引数はテスト実施時は空配列で問題ないです。
$ruleset->registerSniffs($sniffFiles, [], []);
$ruleset->populateTokenListeners();
populateTokenListeners
メソッドは指定したSniffファイルを実行するために呼んでおく必要があるそうです。
PHP_CodeSniffer\Files\LocalFile
のインスタンス
4. 1 ~ 3の内容を組みあせて、PHP_CodeSniffer\Files\LocalFile
のインスタンスを生成します。
process
メソッドを呼び出せば指定したSniffファイルを実行できます。
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Files\LocalFile;
use PHP_CodeSniffer\Ruleset;
$fixtureFile = [__DIR__ . '/tests/PHPCSCustomRules/Sniffs/NamingConventions/fixture.php']
$config = new Config();
$ruleset = new Ruleset($config);
~~~~~ 省略 ~~~~~
$phpcsFile = new LocalFile($fixtureFile, $ruleset, $config);
$phpcsFile->process();
$foundErrors = $phpcsFile->getErrors();
getWarnings
とgetErrors
メソッドを使うことで実行結果を次のような連想配列で取得することが可能です。
最初の階層のキーがエラーが発生している行番号になります。
Array
(
[4] => Array
(
[1] => Array
(
[0] => Array
(
[message] => Variable name "camelCase" is not in snake_case format
[source] => PHPCSCustomRules.NamingConventions.SnakeCaseVariableName.found
[listener] => Naopusyu\PHPCSCustomRules\Sniffs\NamingConventions\SnakeCaseVariableNameSniff
[severity] => 5
[fixable] =>
)
)
)
)
5. Sniffファイル実行結果の検証
今回は想定している箇所(行)を検出できれば良いかと思いますので、Sniffファイル実行後に取得できる行番号が正しいかを検証したいと思います。
1. テストファイルの作成
の内容では、4, 9, 14, 17, 20行目がエラーになるはずです。
$foundErrors = $phpcsFile->getErrors();
$lines = array_keys($foundErrors);
$this->assertSame([4, 9, 14, 17, 20], $lines);
6. すべてを組み合わせる
ここまでの内容を組み合わせると次のようなテストケースになります。
<?php
declare(strict_types=1);
namespace Naopusyu\PHPCSCustomRules\Tests\Sniffs\NamingConventions;
use PHPUnit\Framework\TestCase;
use PHP_CodeSniffer\Files\LocalFile;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Config;
class SnakeCaseVariableNameSniffTest extends TestCase
{
public function test(): void
{
$fixtureFile = __DIR__ . '/fixture.php';
$sniffFiles = [__DIR__ . '/../../../../src/PHPCSCustomRules/Sniffs/NamingConventions/SnakeCaseVariableNameSniff.php'];
$config = new Config();
$ruleset = new Ruleset($config);
$ruleset->registerSniffs($sniffFiles, [], []);
$ruleset->populateTokenListeners();
$phpcsFile = new LocalFile($fixtureFile, $ruleset, $config);
$phpcsFile->process();
$foundErrors = $phpcsFile->getErrors();
$lines = array_keys($foundErrors);
$this->assertSame([4, 9, 14, 17, 20], $lines);
}
}
テストの実行
事前にbootstrapファイルを用意して、テストクラス内でPHP_CodeSnifferを動かすためにPHP_CodeSnifferのtests/bootstrap.phpを読み込んでおきます。
<?php
require_once __DIR__ . '/../vendor/squizlabs/php_codesniffer/tests/bootstrap.php';
テスト実行はPHPUnitと同じです。
$ ./vendor/bin/phpunit --bootstrap ./tests/bootstrap.php ./tests/PHPCSCustomRules/Sniffs/NamingConventions/SnakeCaseVariableNameSniffTest.php
PHPUnit 10.2.2 by Sebastian Bergmann and contributors.
Runtime: PHP 8.2.7
. 1 / 1 (100%)
Time: 00:00.017, Memory: 10.00 MB
OK (1 test, 1 assertion)
まとめ
PHP_CodeSnifferのカスタムルールのテストケースを作成しました。
今回は単純にエラーが発生した行番号の検証だけなので、プロダクトコードを直接検証すればいいみたいな話かもしれないですが、パッケージとして公開している場合はユニットテストがあった方が良いかと思います。
カスタムルールの日本語ドキュメントが少ないのでこれが誰か(n年後の自分)の役に立つことを祈っております。
Discussion