ユニットテストで、pivateやprotectedのメソッドをテストしたい場合があります。 その場合、ReflectionMethodなどを使って行いますが、結構使うので、より簡単に行えるようなクラスを考えてみました。

また、privateやprotectedプロパティへもアクセスしたい場合があるので、同じクラスでアクセス制御できるようにしてみました。

参照 ReflectionMethod, ReflectionPropertyのサンプル

使用方法

クラス名はTestObjectFactoryになります。

TestObjectFactory($class, $methods = array(), $properties = array())

第一引数に対象となるクラスのインスタンスまたはクラス名を指定します。

// インスタンス指定
$obj = new TestObjectFactory(new User(1, 2));

// クラス名指定
$obj = new TestObjectFactory('Customer');

第二引数はアクセス可能にするメソッド名を配列で指定します。 全てのメソッドをアクセス可能にする場合はnullを指定します。

// privateまたはprotectedなメソッド名を指定
$obj = new TestObjectFactory('Customer', array('calcPrice', checkOrder'));

// 全てのメソッドをアクセス可能にする場合
$obj = new TestObjectFactory('Customer', null);

// クラス定義と同じ(publicなメソッドだけアクセス可能)
$obj = new TestObjectFactory('Customer', array());

第三引数はプロパティ指定になります。 全てのプロパティをアクセス可能にする場合はnullを指定します。

// privateまたはprotectedなプロパティ名を指定
$obj = new TestObjectFactory('Customer', array(), array('name', 'tel'));

// 全てのプロパティをアクセス可能にする場合
$obj = new TestObjectFactory('Customer', array(), null);

// クラス定義と同じ(publicなプロパティだけアクセス可能)
$obj = new TestObjectFactory('Customer', array(), array());

メソッド プロパティ
クラス定義と同じ クラス定義と同じ $obj = new TestObjectFactory(new User(1, 2))
指定メソッドのみアクセス許可 クラス定義と同じ $obj = new TestObjectFactory(new User(1, 2), array('getStrPrivate'))
クラス定義と同じ 指定プロパティのみアクセス許可 $obj = new TestObjectFactory(new User(1, 2), array(), array('action'))
全メソッドアクセス許可 クラス定義と同じ $obj = new TestObjectFactory(new User(1, 2), null)
全メソッドアクセス許可 全プロパティアクセス許可 $obj = new TestObjectFactory(new User(1, 2), null, null)

例. テストしたいクラスがUserで、テストしたいprivateメソッドがgetStrPrivateだけの場合、 以下のようにインスタンスを作成します。

$object = new TestObjectFactory(new User(1, 2), array('getStrPrivate'));

テストするときは、$objectのメソッドとして実行します。 期待する戻り値が”private"という文字列の場合、以下のようにします。

$this->assertEquals('private', $object->getStrPrivate());

指定メソッドだけアクセス可能、プロパティは変更なしでテスト

publicでない指定メソッドだけアクセス可能のテストは以下のようになります。

$object = new TestObjectFactory(new User(1, 2), array('getStrPrivate','getStrProtected'));

$this->assertEquals('private', $object->getStrPrivate());
$this->assertEquals('protected', $object->getStrProtected());

全てのメソッドにアクセス可能、プロパティは変更なしでテスト

全メソッドアクセス可能のテストは以下のようになります。

$object = new TestObjectFactory(new User(1, 2), null);

$this->assertEquals('private', $object->getStrPrivate());
$this->assertEquals('protected', $object->getStrProtected());

全てのメソッド、プロパティにアクセス可能でテスト

全メソッドと全プロパティをアクセス可能にする場合は以下のようになります。

$object = new TestObjectFactory(’Customer', null, null));

$this->assertEquals('private', $object->getStrPrivate());
$this->assertEquals('山田太郎', $object->name);

作成したクラスのソース

以下のようになります。 namespaceは使用環境に合わせて変更します。

<?php
namespace Test\Lib;

use Exception;
use ReflectionMethod;
use ReflectionProperty;

/**
 * ユニットテストでprivate、またはprotectedのメソッドを実行したり、privateやprotectedの
 * プロパティにアクセスできるようにするためのクラス。
 *
 * 例 UserクラスのprivateメソッドgetStrPrivateをユニットテストする場合
 *   $method = new MethodAccess(new User(1, 2), array('getStrPrivate'));
 *   $result = $method->getStrPrivate();
 *
 */
class TestObjectFactory
{
    /**
     *  テストするクラスのインスタンスの保存用
     *
     * @var object
     */
    private $___instance;

    /**
     * アクセスを許可するprivateとprotectedのメソッド名を保存する
     *
     * @var array
     */

    private $___methods;

    /**
     * アクセスを許可するprivateとprotectedのプロパティ名を保存する
     *
     * @var array
     */
    private $___properties;

    /**
     * メソッド名をキーとした、ReflectionMethodオブジェクトのキャッシュ用
     *
     * @var array
     */
    private $___cacheMethods;

    /**
     * プロパティ名をキーとしたReflectionClassオブジェクトのキャッシュ用
     *
     * @var array
     */
    private $___cacheProperties;

    /**
     * コンストラクタ
     *
     * $methodsは、アクセスを許可するprivateやprotectedのメソッド名を配列で指定する。
     * nullの場合は、全てのメソッドにアクセスできるようになる。
     * カラの配列の場合は、通常と同じでprivateとprotectedのメソッドにはアクセスできない。
     *
     * $propertiesは、アクセスを許可するprivateやprotectedのプロパティ名を配列で指定する。
     * nullの場合は、全てのプロパティにアクセスできるようになる。
     * カラの配列の場合は、通常と同じでprivateとprotectedのプロパティにはアクセスできない。
     *
     * @param string|object $class
     * @param null|array $methods アクセス許可するメソッド名
     * @param null|array $properites アクセス許可するプロパティ名
     * @throws Exception
     */
    public function __construct($class, $methods = array(), $properties = array())
    {
        if (is_string($class)) {
            $this->___instance = new $class();
        } elseif (is_object($class)) {
            $this->___instance = $class;
        } else {
            throw new Exception("Invalid parameter class");
        }

        if (!is_null($methods) && !is_array($methods)) {
            throw new Exception("Invalid parameter methods");
        }
        if (!is_null($properties) && !is_array($properties)) {
            throw new Exception("Invalid parameter properties");
        }

        $this->___methods = $methods;
        $this->___properties = $properties;
        $this->___cacheMethods = array();
        $this->___cacheProperties = array();
    }

    /**
     * 指定の名前のメソッドが実行されたときに呼び出される。
     * publicか、許可リストにあるprivateとprotectedの
     * メソッドを実行して結果を返す。
     *
     * @param string $name
     * @param array $args
     * @return mixed
     */
    public function __call($name, $args)
    {
        if (isset($this->___cacheMethods[$name])) {
            $method = $this->___cacheMethods[$name];
        } else {
            $method = new ReflectionMethod(get_class($this->___instance), $name);
            if (!$method->isPublic()) {
                if (is_null($this->___methods) || in_array($name, $this->___methods)) {
                    $method->setAccessible(true);
                }
            }
            $this->___cacheMethods[$name] = $method;
        }
        return $method->invokeArgs($this->___instance, $args);
    }

    /**
     * プロパティから値を取得する場合に呼び出される。
     *
     * @param string $name
     * @return mixed
     */
    public function __get($name)
    {
        if (isset($this->___cacheProperties[$name])) {
            $property = $this->___cacheProperties[$name];
        } else {
            $property = new ReflectionProperty(get_class($this->___instance), $name);
            if (!$property->isPublic()) {
                if (is_null($this->___properties) || in_array($name, $this->___properties)) {
                    $property->setAccessible(true);
                }
            }
            $this->___cacheProperties[$name] = $property;
        }
        return $property->getValue($this->___instance);
    }

    /**
     * プロパティに値を設定する場合に呼び出される。
     *
     * @param string $name
     * @param mixed $value
     */
    public function __set($name, $value)
    {
        if (isset($this->___cacheProperties[$name])) {
            $property = $this->___cacheProperties[$name];
        } else {
            $property = new ReflectionProperty(get_class($this->___instance), $name);
            if (!$property->isPublic()) {
                if (is_null($this->___properties) || in_array($name, $this->___properties)) {
                    $property->setAccessible(true);
                }
            }
            $this->___cacheProperties[$name] = $property;
        }
        $property->setValue($this->___instance, $value);
    }
}