つい先日、テスティングフレームワークをsimpletestからPHPUnitに移行しました。
その時にやったこと・困ったことの知見を共有します。
2016年の今に需要のある情報だとは思えませんが、今でもsimpletestでテストを書いていて、そこから脱出したい方のお役に立てれば幸いです。
移行前の状態
14万行くらいのリポジトリの中にsimpletestによるテストコードは5000行くらいある状態で、
カバレッジ計測はしてませんでしたが、それほどテストコードがある状態ではありませんでした。
そのため移行はスムーズに出来ましたが、仮にコード量が多くてもそこまで苦労はしないんじゃないのかなと思います。
何故PHPUnitを選択したのか?
PHPUnitがPHP界隈のテスティングフレームワークでほぼデファクトスタンダードなので安心感がありました。
しかし、なによりも simpletestとほぼメソッドが一緒で移行が楽 なのが最大の理由です。
数時間の作業でさくっと移行出来ちゃいました。
やったこと
phpunit.xmlを作る
composerからPHPUnitを入手してから、まず最初にphpunit.xmlを作りました。
phpunit.xmlとはPHPUnitのconfigurationを指定するファイルで、テスト実行時のカレントディレクトリに置いてあれば自動で読み込んでくれます。
とりあえず最低限必要な項目は以下くらいだと思います。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
bootstrap="vendor/autoload.php"
colors="true"
convertErrorToExceptions="false"
convertNoticesToExceptions="false"
convertWarningToExceptions="false">
</phpunit>
- bootstrap
- 全テストで最初にcomposerのautoloaderを読ませる指定です。
- PHPUnitをcomposerから読み込めなければならないので必須。
- colors
- phpunitの結果出力に色がついて見やすくなります。必須。
- convertXXXXXToExceptions
- テスト中に発生したError/Notices/WarningをExceptionに変換して、テストをコケさせるための設定をfalseでわざと落としています。
- たぶんtrueにするのがあるべき姿ですが、レガシーだとなにかとNoticesやWarningが発生するもの・・・。それらを全部解消しないとテストが通らないのはつらすぎるのでfalseにしています。
既存のテストをPHPUnitで動かす
次に既存のテストケースをPHPUnitで動かすようにしました。
やることは簡単で、テストクラスの継承元を UnitTestCase
から PHPUnit_Framework_TestCase
に変えて、テストクラス名と ファイル名(忘れがち) の接尾に Test
をくっつけるだけ。
ホントこれだけ。
- class MyClass extends UnitTestCase
+ class MyClassTest extends PHPUnit_Framework_TestCase
基本的にテストクラス内部のテストの書き方はsimpletestもPHPUnitもほぼ一緒です。以下のルールはどちらにも当てはまります。なので上の修正だけでだいたい動いちゃいます。
- テストメソッドの接頭に
test
をつける(例:testSameCheckMethod
) - テストケース毎の事前・事後処理を
setUp
とtearDown
に書く - テストケース毎に
$this->assertTrue
などでアサーションを行う
互換性の無いアサーションメソッドを修正する
アサーションメソッドもsimpletestとPHPUnitはほぼ互換性はあるのですが、一部ちょっとだけ違うものがあったりするので修正します。
うちで変換の必要があったのは以下の2つでした。
-
assertTrue
の判定が厳密になった- simpletest: いわゆる
==
と同じ評価で、true, 1, '1'などphp的にtrueなのは全部通る - PHPUnit: いわゆる
===
と同じ評価で、boolean型のtrue
のみを通す
- simpletest: いわゆる
-
assertEqual
やassertPattern
のメソッド名が変わった-
assertEqual
->assertEquals
-
assertPattern
->assertRegExp
- 名前が変わっただけで引数などは一緒です。grep一発置換しましょう。
-
困ったこと
テストオブジェクトの準備方法が違う!
1つだけ困ったことがありまして、テストクラスからテストオブジェクトを作り出しテストを実行するという処理の仕方が、simpletestとPHPUnitで若干違っていました。
まあうちが グローバルオブジェクトに依存したテストをしてしまっている(!?) のが最大の原因なので、世の真面目なテストを作っている方々には関係のない話だと思います!!
- simpletest: テストケース実行直前にテストオブジェクトを作る
- PHPUnit: 事前にテストオブジェクトを全て作ってから、順番にテストケースを実行する
ざっと書くとこんな違いで、ちゃんとしたテストを作っているならたぶん困らないのですが、 コンストラクタでグローバルオブジェクトを作って、そのグローバルオブジェクトを使ったテストを行って いたりすると困ります。
図で書くと以下のような感じです。
こんな感じでPHPUnitは最初にすべてのテストオブジェクトのコンストラクタが実行されるので、そこで設定したグローバルオブジェクトがテストケース実行時点では変わっていたりします。そのせいで謎のテスト失敗が発生してしまい、調査に時間がかかりました・・・。
対応方法はグローバルオブジェクトの初期化を setUp
メソッドに移動して、テストケース実行直前に行わせることで解消しました。
アノテーションに backupGlobals
、backupStaticAttributes
、runInSeparateProcess
などを設定して解消するやり方もあったのですが、この記事では触れません。
最後に
そもそもまだテストコードが少なすぎるので、うちの移行は相当スムーズにいった方だと思います。
ここには僕の移行過程で分かった範囲で書いてますが、他にも地味に違うところはあるかもしれません。
うちでは使ってませんでしたが、simpletestのモック機能はだいぶ違うみたいです。
simpletestのエッジな機能を使っていると、他にも困ったことが出てくるかもしれませんが、メインとなる機能(テストクラス・テストメソッド・アサーション)の範囲ではびっくりするくらい互換性があります。
モックライブラリやテストデータファクトリなど、世の中の便利なテストライブラリはだいたいPHPUnitと親和性が高く作られています。
世の強力なツールによるサポートを受け、開発効率を上げるためにもsimpletestからPHPUnitに移行していきましょう!