モチベーション
なぜ整えるに至ったかについてです。
私はこのパッケージの作者兼メンテナーです。
(以降 本パッケージ
と記載します)
PHP 5.4 で廃止された register_globals
や magic_quotes_gpc
を PHP 5.4 以上でも似たような形で再現してくれる君です。
つまり PHP 5.4以上で動いてほしいパッケージ であり、メンテナーとしても PHP 5.4 以上で使える状態にしておきたい のです。EOL を迎えているとしてもあえてサポートする構えなのです*1。
PHP の各バージョンをサポートするにあたり、やはり自動テストは欠かせないでしょう。PHP パッケージであれば PHPUnit ですよね。 突然ですが下図をご覧ください。使用する PHP バージョン毎に、対応する PHPUnit のバージョンも異なることがわかります。
Supported Versions of PHPUnit – The PHP Testing Framework
これらを踏まえて、こう考えました。 ( 🔥 が本記事のメインテーマです )
- やりたいこと
- やりたいことを達成するまでの懸念
本記事について
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 に全て含まれています。個別でもリンクを貼っておきます。
1. PHPUnit 4 → PHPUnit 6 (PHP 7.2 以降では必須)
課題
PHPUnit 6 で大きく変わったことといえば様々ありますが、今回は以下2つを見ていきます。
Release Announcement for Version 6 of PHPUnit – The PHP Testing Framework
前者については書いてあるとおり、後者については例えばこういうやつです。
つまり下記のようなテストコードだと PHPUnit 7からは動かなくなる、ということです。
<?php use \PHPUnit_Framework_TestCase; // PHPUnit 7 だと存在しないクラス class RequestTest extends PHPUnit_Framework_TestCase { }
それでは PHPUnit\Framework\TestCase
に書き換えればいいじゃん! となるのですが
となってしまい、同じテストコードを維持したまま別々のバージョンの PHPUnit を走らせることができないのです!! 困った!!
対応
いくつか方法がありますが、早いのはこんな感じで。
-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
2. PHPUnit 4 → PHPUnit 8 (PHP 8.0 以降では必須)
課題
PHPUnit 8 で大きく変わったことといえば様々ありますが、今回は以下2つを見ていきます。
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 { // TestCase だと setUp(): void になっている protected function setUp() // ここは指定されていない { } }
じゃあどうするかと考えますが、PHP 5.x だと戻り値の型指定がそもそもできないので、:void
つけても syntax error で怒られていまいます。困った!!
対応
かなり妥協した案です。
そう、つまり 型指定されているメソッドを使わない です。正直かなり苦しい。
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()
の内容を書いていくだけの作業を行いました。
これ以上の規模になってくると、テストコードを分けた方がいいんだろうなという気持ちにはなっています。
3. PHPUnit 4 → PHPUnit 9
課題
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 // (snip) /** * Below annotations are for PHPUnit < 9.0 * * @expectedException PHPUnit_Framework_Error_Warning * @expectedExceptionMessage The session not yet started (Ignoring) */ public function testPolluteSessionNotStarted() { // For PHPUnit >= 9.0 if (method_exists($this, 'expectWarning')) { $this->expectWarning(); $this->expectWarningMessage('The session not yet started (Ignoring)'); } $this->object = new Session; $this->object->pollute(); }
これでOK!!
参考資料
- Update PHPUnit (for PHP 8.0 or earlier) by gongo · Pull Request #17 · gongo/merciful-polluter
- 実際に対応した様子
4. PHPUnit 4 → PHPUnit 10
課題
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 でも動く方法を発見しました。インターネットから。
trigger_error()
が実行されそうな処理の直前で set_error_handler()
をしておき、E_USER_WARNING
を補足するという古き良き対策です。便利。
参考資料
結果
綺麗
まとめ
みんなちゃんと PHP バージョンアップしような! register_globals
や magic_quotes_gpc
から逃れられなくて PHP 5.4 に上げられない人は、まあ頑張ってや!!
*1:使う人おらんやろという気持ちもあるので、そこまで高尚な心持ちではないです