RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

PHPコードを「テストしない」方法

この記事は PHP Advent Calendar 2023 6日目の記事です。

こんにちは、配配メール開発エンジニアの takaram です。

私のチームでは、最近行ったリファクタリングPHPバージョンアップ対応において、あえて修正したコードをテストしないという対応を取ることがありました。この記事ではこれについてお話しします。

テストしないでリリースする方法

いきなりネタバレしてしまうと、これはモノタロウさんのテックブログで紹介されていた以下の記事のn番煎じです。

tech-blog.monotaro.com

要約すると「コードの修正前後で抽象構文木 (AST) が変化しなければ、コードの動作も変わらないはずなのでテスト不要と判断できる」ということです。 元記事はPythonでやっていますが、考え方自体は他の言語でも使えるので、実際にPHPでやってみたという記事もあります。

上記2記事は拡張モジュールのphp-astを利用していますが、拡張モジュールのインストールは面倒であることが多いので、今回はPHP-Parserを使うことにします。

なお、これ以降「ファイルのハッシュ値」という表現を「PHPプログラムのASTのハッシュ値」という意味で使います。

使いどころ

一括コードフォーマット

PHP_CodeSnifferやphp-cs-fixerでプロジェクト全体に自動修正をかけた場合、対象ファイルが数千を超えることもあります。これを全て手動でテストするのは非現実的でしょう。

インデントや演算子の周りの空白の修正であればASTには影響がないため、この手法を使うことができます。

${}による文字列補間の修正

PHP8.2で"${var}"形式で文字列に変数を埋め込む文字列補間が非推奨になりました。 まだ使えなくなるわけではないですが、"{$var}"形式に書き換える必要があります。

これら2つの文字列は同等のASTが出力されるため、ASTのハッシュ値が変わりません。

やり方

まず適当なディレクトリに、ファイルのハッシュ値を出力するスクリプトを作ります。ファイルはこんな感じです。

<?php
declare(strict_types=1);

use PhpParser\NodeDumper;
use PhpParser\ParserFactory;

require_once __DIR__ . '/vendor/autoload.php';

$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$dumper = new NodeDumper();
foreach (array_slice($argv, 1) as $file) {
  try {
    $ast = $parser->parse(file_get_contents($file));
  } catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    exit(1);
  }
  $hash = md5($dumper->dump($ast));
  echo "{$hash}\t{$file}\n";
}

以下では、このファイルを/tmp/php-ast-diff/ast_md5.phpに置いているとして進めます。

次に、nikic/PHP-Parserをインストールします。バージョンはv4.x系を利用します。

$ cd /tmp/php-ast-diff
$ composer require nikic/php-parser:'^4.17'

ここまでで準備は完了です。試しに、ast_md5.php自身のハッシュ値を出力させてみましょう。 以下のように出力されると思います。

$ php ast_md5.php ast_md5.php
7042836d533afb828822f59454abb39f    ast_md5.php

これ以降は、ハッシュ値を計算したいファイルのあるリポジトリで作業します。
また、mainブランチからrefactorブランチを切ってコードを修正したと仮定します。

まず、修正したファイルを一覧で出しておきます。

$ git diff --name-only main...refactor > changed_files

次に、修正前のファイルのハッシュ値を出力します。
mainブランチ上だと、refactorブランチ作成後にマージされた他の変更が入っている場合があるため、ブランチの分岐の根元に当たるコミットにチェックアウトします。

$ git checkout "$(git merge-base main refactor)"
$ xargs php /tmp/php-ast-diff/ast_md5.php < changed_files > hash_before

次に修正後のファイルのハッシュ値を出力します。

$ git checkout refactor
$ xargs php /tmp/php-ast-diff/ast_md5.php < changed_files > hash_after

最後にhash_beforehash_afterをdiffコマンド等で比較し、差分がなければハッシュ値が変わっていないと分かります。

最後に

特にユニットテストが十分にないプロダクトでは、リファクタリングは往々にして大変になりがちです。
今回紹介した方法であればすぐにでも取り入れられるので、ぜひ活用してみてください!

Copyright © RAKUS Co., Ltd. All rights reserved.