モチベーション
なぜ整えるに至ったかについてです。
私はこのパッケージの作者兼メンテナーです。
github.com
(以降 本パッケージ
と記載します)
PHP 5.4 で廃止された register_globals
や magic_quotes_gpc
を PHP 5.4 以上でも似たような形で再現してくれる君です。
つまり PHP 5.4以上で動いてほしいパッケージ であり、メンテナーとしても PHP 5.4 以上で使える状態にしておきたい のです。EOL を迎えているとしてもあえてサポートする構えなのです*1。
PHP の各バージョンをサポートするにあたり、やはり自動テストは欠かせないでしょう。PHP パッケージであれば PHPUnit ですよね。
突然ですが下図をご覧ください。使用する PHP バージョン毎に、対応する PHPUnit のバージョンも異なることがわかります。
![PHPUnitがサポートしているPHPのバージョン一覧絵](https://cdn-ak.f.st-hatena.com/images/fotolife/g/gongoZ/20230830/20230830114625.png)
Supported Versions of PHPUnit – The PHP Testing Framework
これらを踏まえて、こう考えました。 ( 🔥 が本記事のメインテーマです )
- やりたいこと
- 本パッケージのアプリケーションコードは PHP 5.4 から PHP 8.2 (2023年8月現在) まで動く構文のみで実装している
- ↑ を保証するために、PHP の各バージョンで自動テストを走らせたい。PHPUnit で
- やりたいことを達成するまでの懸念
- PHPUnit はバージョン毎に(当然だが)サポートする PHP バージョンや機能があったり無かったりする
- なるべく PHPUnit のバージョンは上げたいが、そうもいかない
- 🔥 つまりアプリケーションコードだけでなく、テストコードも 同じコードのままあらゆるバージョンのPHPUnitで動くように揃えたい
- テストコードは PHPUnit のバージョンに合わせて複数用意する手もあるが、メンテコストがやばそうなので、最終手段としたい
本記事について
PHP 5.4 から PHP 8.2 (PHPUnit で言えば 4 から 9) までを 同じアプリケーションコード、同じテストコードのまま CI に乗せるための試行錯誤の記録となります。
期待値調整
まず PHP のバージョン差異による対応(※1)については今回主題ではないので書いていません。あくまで PHPUnit の話だけを書いています。
※1 例えば配列の分割代入で、list()
じゃなくて[]
使えるようになったのはPHP 7.1からだよ、とかそういうやつ
小さいパッケージなので、対応したこと自体はそこまで多くありません。そのためタイトルに書いてある状況と似た 他のパッケージすべてに適用できるような内容ではありません 。
?「うちのパッケージで同じことやろうとしたけど、コレも足りひんアレも足りひんで、この記事の内容だけだと全然網羅できてなかったやん!」
僕「それはそう」
本記事で書いた対応は 「これがベスト!」というものでも無い と考えています。より良い解決方法をお持ちの方は是非コメントお願いします。
対応リスト
具体的な実装は Release Release v0.0.6 · gongo/merciful-polluter · GitHub に全て含まれています。個別でもリンクを貼っておきます。
課題
PHPUnit 6 で大きく変わったことといえば様々ありますが、今回は以下2つを見ていきます。
Release Announcement for Version 6 of PHPUnit – The PHP Testing Framework
前者については書いてあるとおり、後者については例えばこういうやつです。
- PHPUnit 6 未満
PHPUnit_Framework_TestCase
- PHPUnit 6 以上
PHPUnit\Framework\TestCase
つまり下記のようなテストコードだと PHPUnit 7からは動かなくなる、ということです。
<?php
use \PHPUnit_Framework_TestCase;
class RequestTest extends PHPUnit_Framework_TestCase
{
}
それでは PHPUnit\Framework\TestCase
に書き換えればいいじゃん! となるのですが
となってしまい、同じテストコードを維持したまま別々のバージョンの PHPUnit を走らせることができないのです!! 困った!!
対応
いくつか方法がありますが、早いのはこんな感じで。
- PHP 5.x で使う PHPUnit を 4.8.35 以上にする
- テストコードをこんな感じで書き換える
-use \PHPUnit_Framework_TestCase;
+use PHPUnit\Framework\TestCase;
-class RequestTest extends PHPUnit_Framework_TestCase
+class RequestTest extends TestCase
{
}
PHPUnit 4.8.35 から ForwardCompatibility/TestCase.php が用意されています。これを利用することで
- PHPUnit 6 未満
PHPUnit\Framework\TestCase
は PHPUnit_Framework_TestCase
を継承した abstract class
- PHPUnit 6 以上
PHPUnit\Framework\TestCase
はそのままのとおり
となります。つまり PHPUnit 6 未満でも以上でも同じテストコードで PHPUnit を実行できるということです。やったね!!
参考資料
- PHP5.3 対応でも PHPUnit は 6 スタイルの記述へ移行しよう - Qiita
- Use PHPUnit 7.5 for support PHP 7.4 by zonuexe · Pull Request #13 · gongo/merciful-polluter · GitHub
- ForwardCompatibility が入っていない PHPUnit を使う場合は自分のリポジトリ内で同じコードを用意しよう、の例
課題
PHPUnit 8 で大きく変わったことといえば様々ありますが、今回は以下2つを見ていきます。
- PHP 7.2 以上を要件とするようになった
- PHPUnit の一部のメソッドに戻り値の型が指定された
Release Announcement for Version 8 of PHPUnit – The PHP Testing Framework
前者については書いてあるとおり、後者については、こういうエラーが出るようになります。
PHP Fatal error: Declaration of Gongo\MercifulPolluter\Test\BaseTest::setUp() must be compatible with PHPUnit\Framework\TestCase::setUp(): void
これはつまりテストコードで継承している setUp で戻り値の型が指定されていないが TestCase.setUp() では定義されているで! だから違うで! というものです。
<?php
use PHPUnit\Framework\TestCase;
class BaseTest extends TestCase
{
protected function setUp()
{
}
}
じゃあどうするかと考えますが、PHP 5.x だと戻り値の型指定がそもそもできないので、:void
つけても syntax error で怒られていまいます。困った!!
対応
かなり妥協した案です。
Update PHPUnit (for PHP 8.0 or earlier) by gongo · Pull Request #17 · gongo/merciful-polluter · GitHub
そう、つまり 型指定されているメソッドを使わない です。正直かなり苦しい。
class FooTest extends TestCase
{
- protected function setUp()
- {
- $this->object = new Foo;
- }
public function testFoo()
{
+ $this->object = new Foo;
$this->assertEquals('foo', $this->object->foo());
}
}
今回は setUp()
だけで、対象となるテストメソッドもそんなに多くなかったので、ひたすら各テストメソッドの setUp()
の内容を書いていくだけの作業を行いました。
これ以上の規模になってくると、テストコードを分けた方がいいんだろうなという気持ちにはなっています。
課題
Release Announcement for Version 9 of PHPUnit – The PHP Testing Framework
今回の対象パッケージで引っ掛かったのはこちらの項目です。
Backward Incompatible Changes
The following functionality was removed:
- Annotation(s) for expecting exceptions
アノテーションとは、今回の文脈でいうとこちらのコードです。
merciful-polluter/test/SessionTest.php at 0.0.5 · gongo/merciful-polluter · GitHub
trigger_error
で E_USER_WARNING
を投げるコードがある
- PHPUnit は
E_USER_WARNING
を例外に変換してしまう
- それを補足するために
@expectedException
アノテーションを書いておく
みたいな儀礼が存在しました。しかし PHPUnit 9 からはそのアノテーションによる補足機能を廃止することとなりました。困った!!
対応
代用としてか PHPUnit 9 から expectWarning()
が誕生したので、そちらを使うようにしてみました。
<?php
@expectedException
@expectedExceptionMessage
public function testPolluteSessionNotStarted()
{
if (method_exists($this, 'expectWarning')) {
$this->expectWarning();
$this->expectWarningMessage('The session not yet started (Ignoring)');
}
$this->object = new Session;
$this->object->pollute();
}
expectWarning()
が定義されているならそっちを使う
expectWarning()
が定義されていないならアノテーションの機能にお任せする
これでOK!!
参考資料
課題
PHPUnit 10 に上げる必要性はこの時点ではなかったのですが、前項の対応を行った状態で PHPUnit を走らせると以下のような警告を貰います。
1) Gongo\MercifulPolluter\Test\SessionTest::testPolluteSessionNotStarted
Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.
PHPUnit 10 までの短い命だったのか……困った!!
対応
@expectedWarning
アノテーションや expectWarning()
を使わない方法、かつ PHP 5.x でも動く方法を発見しました。インターネットから。
github.com
trigger_error()
が実行されそうな処理の直前で set_error_handler()
をしておき、E_USER_WARNING
を補足するという古き良き対策です。便利。
参考資料
結果
![php5.4から8.2まですべてテストをpassしている](https://cdn-ak.f.st-hatena.com/images/fotolife/g/gongoZ/20230830/20230830181315.png)
綺麗
まとめ
みんなちゃんと PHP バージョンアップしような! register_globals
や magic_quotes_gpc
から逃れられなくて PHP 5.4 に上げられない人は、まあ頑張ってや!!