Thanks Driven Life

日々是感謝

PHPUnit + Selenium で撮れるスクリーンショットのファイル名を任意に設定する

概要

業務で PHPUnit + Selenium (PHPUnit_Extension_SeleniumTestCase) を使っていて、
最近エラーが出たら、その時の画面のスクリーンショットを撮る機能を導入しました。

かなり便利だなーと思うんですが、一個だけ不満があって
生成される画像のファイル名が英数字の羅列なんです。

/tmp/26ae6952ce20eaa37a762cbc52428640.png

こんな感じで。
もちろんテスト実行時に「このテストケースでこのファイルが作られました」みたいなログは出るのですが
いちいち画像ディレクトリとログを見比べてどれがどれやねんってやるのがめんどくさかったです。

というわけで、生成されるファイル名がこちらで設定できないかと考えました。

PHPUnit_Extension_SeleniumTestCase を覗く

おそらくここでしょう。

   /**
     * Take a screenshot and return information about it.
     * Return an empty string if the screenshotPath and screenshotUrl
     * properties are empty.
     * Issue #88.
     * 
     * @access protected
     * @return string
     */
    protected function takeScreenshot()
    {
        if (!empty($this->screenshotPath) &&
            !empty($this->screenshotUrl)) {
            $filename = $this->getScreenshotPath() . $this->testId . '.png';

            $this->drivers[0]->captureEntirePageScreenshot($filename);

            return 'Screenshot: ' . $this->screenshotUrl . '/' .
                   $this->testId . ".png\n";
        } else {
            return '';
        }
    }

$this->testId が英数字の羅列になると思います。こいつをテストケース名にしてしまえば幸福が実現します。
さて、ここでテストケース名は誰が持っているかが問題ですが、答えは自分自身 (正確には PHPUnit_Framework_TestCase ) です。
じゃあ $this->name でいけるよね、と思ったらそれができません。
なぜなら、 PHPUnit_Framework_TestCase::name は private なのです。さてどうするか。


追記 5/29 15:35


何も苦労することなかった!やべー完全に見逃してたわやべー。
@ さんありがとうございます!

というわけで、以下の Reflection の部分は必要なくなったので、
Reflection はどうでもいいっていう方は一番下まで飛んでください。


みんなの味方 Reflection

PHP にはリフレクションと呼ばれる便利機能があります

PHP 5には完全なリフレクション API が付属しており、 クラス、インターフェイス、関数、メソッド、そして拡張モジュールについて リバースエンジニアリングを行うことができます。

http://www.php.net/manual/ja/intro.reflection.php

いろいろ深い機能なのですが、ものすごいざっくり一言で言うと「private なんてものはなかった」ということが可能です。
そういうわけでやってみましょう。今回の目的は「private property である PHPUnit_Framework_TestCase::name をサブクラスで読みたい」です。
プロパティを覗きたいので、ReflectionProperty クラス を使います。

// PHPUnit_Framework_TestCase::name に関するリフレクションの準備
$reflection = new ReflectionProperty('PHPUnit_Framework_TestCase', 'name');

// private である PHPUnit_Framework_TestCase::name を、どっからでもアクセス可能(public)にします
$reflection->setAccessible(true);

// getValue() に $this (PHPUnit_Extension_SeleniumTestCase のインスタンス) を渡すことで
// 本来親クラスで private となっている name を取得することができます。
$testName = $reflection->getValue($this);

ほら簡単。
こんな感じで、親クラスにのみ読み取り可能であったプロパティを無事子クラスでも取得できました。
もちろん親子関係なく無関係のクラスで protected or private なものにもアクセスすることができます。

最終的にこうなりました


※ 以下のコードは、そのままリフレクション使ったままにしてあります。
  上述したように、PHPUnit_Framework_Testcase::getName(false) にすればいいんですが
  せっかくなので残しておきます。


ファイル名は、「テストクラス名++テストケース名」としました。ケースだけだと被りそうだったので。
ちなみに GongoBaseTest::takeScreenshot は、追加分以外は本家 SeleniumTestCase.php のままです。

こんだけですね。

リフレクション便利


実コードに気軽につかっていくと、すぐ黒くなっていくので注意したいところ。
しかし、こういった実コードとは別の所になら全然いけるんじゃないでしょうか。

余談ですが、たしか setAccessible って 5.2 だとまだ使えなかった記憶があって、
5.3 になってからまだまだ PHP やっていけるなって思うように頑張っています。