PHPUnit自动加载类

时间:2015-06-19 07:38:08

标签: php yii phpunit autoload

简短问题

如何让Autoloader找到运行PHP测试所需的所有类?

详细问题

我想自动加载我在Eclipse中的PHPUnit中使用的类。我的目录结构如下。

Project (called yii-app)
    protected
        dirA
            classA.php
        dirB
            classB.php
    yii-1.1.14.f0fee9
        Yii.php
    tests
        ClassATest.php
        ClassBTest.php
        bootstrap.php
        Autoloader.php

我使用bootstrap.phpAutoloader.php找到here,详见下文。类classA没有使用Yii框架,ClassATest中的测试运行顺利。类classB确实使用了Yii框架。第一行之一是:

Yii::import('application.<path into some directory>.*')

当我尝试在ClassBTest.php中运行测试时,出现以下错误。

Fatal error: Class 'Yii' not found in /Users/physicalattraction/git/yii-app/protected/dirB/classB.php on line 3

即使我注册了整个项目目录(包括子目录),也找不到类Yii,而它就在那里。我应该改变什么才能使这些测试运行?

注意

如果我尝试直接从终端运行测试,我遇到同样的问题,因此它与Eclipse无关。

$ ./composer/vendor/bin/phpunit --bootstrap=tests/bootstrap.php tests
PHPUnit 4.5.1 by Sebastian Bergmann and contributors.

Fatal error: Class 'Yii' not found in /Users/physicalattraction/git/yii-app/protected/dirB/classB.php on line 3

详情

Eclipse中的PHPUnit设置

PHPUnit settings in Eclipse

bootstrap.php中

<?php
include_once('AutoLoader.php');
// Register the directory to your include files
Toolbox\Testing\AutoLoader::registerDirectory(__DIR__.'/../yii-1.1.14.f0fee9');
Toolbox\Testing\AutoLoader::registerDirectory(__DIR__.'/../protected');
?>

Autoloader.php

    <?php

namespace Toolbox\Testing;

/**
 * This class is an auto loader for use with vanilla PHP projects' testing environment. Use it in
 * the bootstrap to register classes without having to use a framework (which you can, and should if
 * it's a better solution for you) and without having to use includes everywhere.
 *
 * It assumes that the file path in relation to the namespace follows the PSR-0 standard.
 *
 * IMPORTANT NOTE: When just registering directories, the class has no ability to discern
 * conflicting class names in different namespaces, which means that classes with the same name will
 * override each other! Always use the registerNamespace()-method if possible!
 *
 * Inspired by Jess Telford's AutoLoader (http://jes.st/).
 *
 * @see http://jes.st/2011/phpunit-bootstrap-and-autoloading-classes/
 * @see http://petermoulding.com/php/psr
 * @see http://www.php-fig.org/psr/psr-0/
 *
 * @codeCoverageIgnore
 *
 * @category    Toolbox
 * @package     Testing
 *
 * @author      Helge Söderström <helge.soderstrom@schibsted.se>
 */

class AutoLoader {

    /**
     * An array keeping class names as key and their path as the value for classes registered with
     * AutoLoader::registerNamespace().
     *
     * @var array
     */
    protected static $namespaceClassNames = array();

    /**
     * An array keeping class names as key and their path as the value for classes registered with
     * AutoLoader::registerDirectory().
     *
     * @var array
     */
    protected static $directoryClassNames = array();


    /**
     * Store the filename (sans extension) & full path to all ".php" files found for a namespace.
     * The parameter should contain the root namespace as the key and the directory as a value.
     *
     * @param string $namespace
     * @param string $dirName
     * @return void
     */
    public static function registerNamespace($namespace, $dirName) {
        $directoryContents = new \DirectoryIterator($dirName);
        foreach($directoryContents as $file) {
            if ($file->isDir() && !$file->isLink() && !$file->isDot()) {
                $newNamespace = $namespace . "_" . $file->getFileName();
                $newDirName = $dirName . "/" . $file->getFilename();
                static::registerNamespace($newNamespace, $newDirName);
            } elseif (substr($file->getFilename(), -4) === '.php') {
                $className = substr($file->getFilename(), 0, -4);
                $namespacedClassName = $namespace . "_" . $className;
                $fileName = realpath($dirName) . "/" . $file->getFilename();
                static::$namespaceClassNames[$namespacedClassName] = $fileName;
            }
        }
    }


    /**
     * Store the filename (sans extension) & full path of all ".php" files found.
     *
     * NOTE: This method will not be able to differentiate the same class names in different
     *       namespaces and will therefore overwrite class names if multiple of the same name is
     *       found. If possible, use registerNamespace instead!
     *
     * @param string $dirName
     * @return void
     */
    public static function registerDirectory($dirName) {
        $directoryContents = new \DirectoryIterator($dirName);
        foreach ($directoryContents as $file) {
            if ($file->isDir() && !$file->isLink() && !$file->isDot()) {
                // Recurse into directories other than a few special ones.
                static::registerDirectory($file->getPathname());
            } elseif (substr($file->getFilename(), -4) === '.php') {
                // Save the class name / path of a .php file found.
                $className = substr($file->getFilename(), 0, -4);
                AutoLoader::registerClass($className, $file->getPathname());
            }
        }
    }


    /**
     * Caches a found class with the class name as key and its path as value for use when loading
     * on the fly. The class is registered with its class name only, no namespace.
     *
     * @param string $className
     * @param string $fileName
     * @return void
     */
    public static function registerClass($className, $fileName) {
        AutoLoader::$directoryClassNames[$className] = $fileName;
    }


    /**
     * Includes a found class in the runtime environment. Strips namespaces.
     *
     * @param string $className
     * @return void
     */
    public static function loadClass($className) {
        // First, see if we've registered the entire namespace.
        $namespacedClassName = str_replace('\\', '_', $className);
        if (isset(static::$namespaceClassNames[$namespacedClassName])) {
            require_once(static::$namespaceClassNames[$namespacedClassName]);
            return;
        }

        // Nope. Have we registered it as a directory?
        $psrDirectorySeparators = array('\\', '_');
        foreach($psrDirectorySeparators as $separator) {
            $separatorOccurrence = strrpos($className, $separator);
            if($separatorOccurrence !== false) {
                $className = substr($className, $separatorOccurrence + 1);
                break;
            }
        }

        if (isset(AutoLoader::$directoryClassNames[$className])) {
            require_once(AutoLoader::$directoryClassNames[$className]);
        }
    }

}

// Register our AutoLoad class as the system auto loader.
spl_autoload_register(array('Toolbox\Testing\AutoLoader', 'loadClass'));
?>

2 个答案:

答案 0 :(得分:1)

Autoloader可能找不到YII类。您尝试添加:

Toolbox\Testing\AutoLoader::registerDirectory(DIR.'/../yii-1.1.14.f0fee9/framework');

到你的bootstrap.php文件。我认为YII类是在框架目录中定义的。

您可以尝试的另一件事是使用composer自动加载器。

P.S 最好在tests目录中镜像您的app目录/文件结构。在你的情况下,ClassATest.php和ClassBTest.php应该被分成它们自己的目录,就像它们在受保护目录中分开一样。

答案 1 :(得分:0)

我发现通过以下更改,它确实有效。

具有以下目录结构

project
    protected
        config
            main.php
            test.php
        controllers
        models
        tests
            fixtures
            functional
            report
            unit
            bootstrap.php
            phpunit.xml

main.php中:添加Yii查找类的目录。 Yii不会自动搜索子目录,因此您必须在此单独指定每个目录。

// autoloading model and component classes
'import' => array(
    'application.models.*',
    'application.models.support.*',
    'application.components.*',
    ....

test.php中定义以下测试配置。

<?php
return CMap::mergeArray(
    require(dirname(__FILE__).'/main.php'),
    array(
        'components'=>array(
            'fixture'=>array(
                'class'=>'system.test.CDbFixtureManager',
            ),
            'db'=>array(
                'class' => 'CDbConnection',
                'connectionString' => 'CONNECTIONSTRING',
                'emulatePrepare' => true,
                'username' => 'USERNAME',
                'password' => 'PASSWORD',
                'charset' => 'utf8',
            ),
        ),
    )
);
?>

然后,在bootstrap文件中,仅使用Yii Autoloader。

<?php
$yiit=__DIR__.'/../../yii-1.1.14.f0fee9/yiit.php';
$config=dirname(__FILE__).'/../config/test.php';
require_once($yiit);
// Include the following line if you want to write a WebTestCase
// require_once(dirname(__FILE__).'/WebTestCase.php');
Yii::createWebApplication($config);
?>

我的测试用例需要一个Order类型的模型,它连接到数据库表orders。在目录fixtures/Order.php中定义一个夹具。

<?php    
return array(
    'order1' => array(
        'id' => 1011,
        'order_number' => 'on_101'
    )
);
?>

按照Yii和PHPUnit网站上的说明制作测试用例。

class MyExportTest extends CDbTestCase
{
    public $fixtures = array(
        'orders' => 'Order'
    );

    public function test_order()
    {
        $order = $this->orders('order1');
        $this->assertTrue($order instanceof Order);
        $r = $order->order_number;
        $e = "on_101";
        $this->assertEquals($r, $e,
        sprintf("Received order_number: \n%s\nExpected order_number: \n%s\n", $r, $e));
    }

    public function test_export()
    {
        $sut = new MyExport($tenantId, $userGroupId);
        $r = $sut->method_to_test()
        $e = "expected result";
        $this->assertEquals($r, $e,
        sprintf("Received result: \n%s\nExpected result: \n%s\n", $r, $e));
    }
}