はじめに
この記事は PHP Advent Calendar 2019 - Qiita 4日目の記事です。
昨日の記事は tzmfreedom さんの PHPでPHPを実装する というすごいタイトルの記事でした。
去年のPHPアドベントカレンダーで PHP 5.6 から PHP 7.2 にする話を書きました。
この記事がめちゃめちゃ好評で、今でもたまに見られています。PHP 5.6 のサポートはとっくに終わったはずなのですが...
ということで今回は PHPUnit 5 を PHPUnit 6 に上げた時の話をします。ちなみに PHPUnit 6 はもうサポートが終了していますので、はやく PHPUnit 7 にする必要があります。俺たちの戦いは これからだ!
PHPUnit 6 にするきっかけ
先程の記事にもありましたが、PHP を 5.6 から 7.2 にする仕事をしていました。php 7.2 に対応するには最低でも PHPUnit 6 まで挙げなければなりません。
これに気づいた時、使用していた PHPUnit のバージョンは 4 でした。
PHPUnit 4 系の最新にすると、deprecatedな使い方をしている箇所でWarningメッセージが出るようになります。これでなんとか PHPUnit 5 にしたのですが、PHPUnit 5 にした瞬間... なんとその Warning の数 1000 個...
これを地道に潰していきます。
地道な修正作業
実際に PHPUnit 6 でテストを回しながら、落ちた箇所を一つ一つ直していきます。CI環境は PHPUnit 5 のままなので、 PHPUnit 5 で使えないモノは使わないよう注意します。
クラスの namespace
PHPUnit_Framework_Testcase
PHPUnit_Framework_TestListener
、PHPUnit_Framework_Test
などのクラスが
PHPUnit\Framework\Testcase
PHPUnit\Framework\TestListener
、PHPUnit\Framework\Test
に変わりました。
PHPUnit\Framework\Testcase
に関しては PHPUnit_Framework_Testcase
との共存期間(両方使える期間)があったのですが、他のクラスは何故か共存期間がありませんでした。何故なのだろうか。
mt_rand と mt_srand
PHPには mt_rand
という乱数を発生させる関数がありますが、実はこれはバグっていました。
このバグが PHP 7.1 で修正されました。
https://www.php.net/manual/ja/function.mt-rand.php
mt_srand
("s" rand です)は、 mt_rand
で使う乱数のseedを固定し、乱数に再現性をもたせることができるメソッドです。*1 これで乱数を固定させてテストしているコードがあり、PHPのバージョンを上げたことで、そのテストが落ちるようになりました。*2
mt_rand
は、第二引数に MT_RAND_PHP
を指定すると、これまでの間違った挙動を維持できるようになっています。こういうところがPHPっぽいですね。*3
グローバル変数の扱い
PHPUnit 5 では、グローバル変数はテストケースごとにリセットされていました。PHPUnit 6 からはリセットされなくなりました。
古くて凶悪なテストの中には $_GET
などのグローバル変数を直接弄っているテストがあり、他のテストに影響を及ぼしていることがありました。
これがなかなか難題で、単体でテスト回すと通るけど、通しで回すと落ちるのです。依存関係もよくわからず修正が困難を極めることもありました。
@backupGlobals
とか --strict-global-state
で以前の仕様を維持できますが、テストが激しく遅くなるのであまりおすすめしません。修正方法がわかんなかったら仕方ないですけどね...。
non-numeric test phpunit6 ochiru
stringとintの比較をしているコードがあり、問題なく動くのですが、PHPUnit 6 で動かすと non numeric な値と比較するのヤメレ!とか言われてテストが落ちます。
<?php // $request_time は string 型になる $request_time = $_GET['request_time']; // $current_time と TOKEN_EXPIRE_SEC は int // PHPUnitだと何故かここでエラーになる if ($request_time > $current_time + TOKEN_EXPIRE_SEC) { .... }
多分 error_reporting の設定の違いが原因ですが、何故落ちているかよくわからなくて、修正終盤で疲れていた僕のプルリクタイトルがこちらです。
アサーションをしていないテスト
「Exceptionが発生しなければOK」としてあるテストがいくつかあり、そのテストの中では assertSame
のようなアサーションが一切ありませんでした。
PHPUnit 6 から、アサーションの無いテストは「アサーション忘れてるぞ!」と言われ、落ちるようになりました。
アサーションが不要な場合はDocコメントに @doesNotPerformAssertions
を記入しておきましょう。
アサーションがほんとに忘れられている場合もありました!何故かifで結果を判定しているのもありました!!アサーションをつかってください!!!
PHPUnit 6 にしてよかったこと
アサーション忘れに気づくようになり、その他の面でもかなり使いやすくなっているのですが、一番の恩恵は高速化です。
PHP 5.6/PHPUnit 5 -> PHP 7.2/PHPUnit 6 にするだけでテストの時間が20分から4分程度になりました。3台あったCI環境ですが、1台ですむようになりました。
PHP7による高速化や、PHPUnit6のglobal stateなどによる高速化があったんだと思います。突然5倍の速さになりました。ユニットテストって純粋なPHPの速度依存なので、バージョン上げるだけでここまでの劇的改善となりました。PHP7が早いというのは本当だった。*4
そして時代は
PHPUnit 6 はEOLを迎えているので、すぐに PHPUnit 7 にしなきゃいけないんですけどね。。。
*1:"s"は多分seedのs
*2:正確には、これはPHPUnitではなく、PHPのバージョンアップによるものなのですが、PHPUnitのバージョンを上げる時に初めて気づいたのでここで書いちゃってます。
*3:他にも、implodeは引数の順番を入れ替えても正常に動作するなど、PHPには互換性維持のための面白い仕様がいくつかあります。 https://www.php.net/manual/ja/function.implode.php
*4:本番アプリケーションにPHP7を適用した際は、微妙に早くなったのですが、そこまで劇的な改善ではなく。。。これは結局、PHPの処理時間よりも、APIのやり取りとかのネットワーク部分だったり、PHP以外の部分の方が、レスポンス時間に占める割合が多かったからなんですよね。一方ユニットテストは純粋にPHPの速さですからね。