mock_objects.php

来自「PHP 知识管理系统(基于树结构的知识管理系统), 英文原版的PHP源码。」· PHP 代码 · 共 1,582 行 · 第 1/4 页

PHP
1,582
字号
    var $_mock_class;
    var $_mock_base;
    var $_reflection;

    /**
     *    Builds initial reflection object.
     *    @param string $class        Class to be mocked.
     *    @param string $mock_class   New class with identical interface,
     *                                but no behaviour.
     */
    function MockGenerator($class, $mock_class) {
        $this->_class = $class;
        $this->_mock_class = $mock_class;
        if (! $this->_mock_class) {
            $this->_mock_class = 'Mock' . $this->_class;
        }
        $this->_mock_base = SimpleTest::getMockBaseClass();
        $this->_reflection = new SimpleReflection($this->_class);
    }

    /**
     *    Clones a class' interface and creates a mock version
     *    that can have return values and expectations set.
     *    @param array $methods        Additional methods to add beyond
     *                                 those in th cloned class. Use this
     *                                 to emulate the dynamic addition of
     *                                 methods in the cloned class or when
     *                                 the class hasn't been written yet.
     *    @access public
     */
    function generate($methods) {
        if (! $this->_reflection->classOrInterfaceExists()) {
            return false;
        }
        $mock_reflection = new SimpleReflection($this->_mock_class);
        if ($mock_reflection->classExistsSansAutoload()) {
            return false;
        }
        $code = $this->_createClassCode($methods ? $methods : array());
        return eval("$code return \$code;");
    }
    
    /**
     *    Subclasses a class and overrides every method with a mock one
     *    that can have return values and expectations set. Chains
     *    to an aggregated SimpleMock.
     *    @param array $methods        Additional methods to add beyond
     *                                 those in the cloned class. Use this
     *                                 to emulate the dynamic addition of
     *                                 methods in the cloned class or when
     *                                 the class hasn't been written yet.
     *    @access public
     */
    function generateSubclass($methods) {
        if (! $this->_reflection->classOrInterfaceExists()) {
            return false;
        }
        $mock_reflection = new SimpleReflection($this->_mock_class);
        if ($mock_reflection->classExistsSansAutoload()) {
            return false;
        }
        if ($this->_reflection->isInterface() || $this->_reflection->hasFinal()) {
            $code = $this->_createClassCode($methods ? $methods : array());
            return eval("$code return \$code;");
        } else {
            $code = $this->_createSubclassCode($methods ? $methods : array());
            return eval("$code return \$code;");
        }
    }

    /**
     *    Generates a version of a class with selected
     *    methods mocked only. Inherits the old class
     *    and chains the mock methods of an aggregated
     *    mock object.
     *    @param array $methods           Methods to be overridden
     *                                    with mock versions.
     *    @access public
     */
    function generatePartial($methods) {
        if (! $this->_reflection->classExists($this->_class)) {
            return false;
        }
        $mock_reflection = new SimpleReflection($this->_mock_class);
        if ($mock_reflection->classExistsSansAutoload()) {
            trigger_error('Partial mock class [' . $this->_mock_class . '] already exists');
            return false;
        }
        $code = $this->_extendClassCode($methods);
        return eval("$code return \$code;");
    }

    /**
     *    The new mock class code as a string.
     *    @param array $methods          Additional methods.
     *    @return string                 Code for new mock class.
     *    @access private
     */
    function _createClassCode($methods) {
        $implements = '';
        $interfaces = $this->_reflection->getInterfaces();
        if (function_exists('spl_classes')) {
            $interfaces = array_diff($interfaces, array('Traversable'));
        }
        if (count($interfaces) > 0) {
            $implements = 'implements ' . implode(', ', $interfaces);
        }
        $code = "class " . $this->_mock_class . " extends " . $this->_mock_base . " $implements {\n";
        $code .= "    function " . $this->_mock_class . "() {\n";
        $code .= "        \$this->" . $this->_mock_base . "();\n";
        $code .= "    }\n";
        if (in_array('__construct', $this->_reflection->getMethods())) {
            $code .= "    " . $this->_reflection->getSignature('__construct') . " {\n";
            $code .= "        \$this->" . $this->_mock_base . "();\n";
            $code .= "    }\n";
        }
        $code .= $this->_createHandlerCode($methods);
        $code .= "}\n";
        return $code;
    }

    /**
     *    The new mock class code as a string. The mock will
     *    be a subclass of the original mocked class.
     *    @param array $methods          Additional methods.
     *    @return string                 Code for new mock class.
     *    @access private
     */
    function _createSubclassCode($methods) {
        $code  = "class " . $this->_mock_class . " extends " . $this->_class . " {\n";
        $code .= "    var \$_mock;\n";
        $code .= $this->_addMethodList(array_merge($methods, $this->_reflection->getMethods()));
        $code .= "\n";
        $code .= "    function " . $this->_mock_class . "() {\n";
        $code .= "        \$this->_mock = &new " . $this->_mock_base . "();\n";
        $code .= "        \$this->_mock->disableExpectationNameChecks();\n";
        $code .= "    }\n";
        $code .= $this->_chainMockReturns();
        $code .= $this->_chainMockExpectations();
        $code .= $this->_chainThrowMethods();
        $code .= $this->_overrideMethods($this->_reflection->getMethods());
        $code .= $this->_createNewMethodCode($methods);
        $code .= "}\n";
        return $code;
    }

    /**
     *    The extension class code as a string. The class
     *    composites a mock object and chains mocked methods
     *    to it.
     *    @param array  $methods       Mocked methods.
     *    @return string               Code for a new class.
     *    @access private
     */
    function _extendClassCode($methods) {
        $code  = "class " . $this->_mock_class . " extends " . $this->_class . " {\n";
        $code .= "    var \$_mock;\n";
        $code .= $this->_addMethodList($methods);
        $code .= "\n";
        $code .= "    function " . $this->_mock_class . "() {\n";
        $code .= "        \$this->_mock = &new " . $this->_mock_base . "();\n";
        $code .= "        \$this->_mock->disableExpectationNameChecks();\n";
        $code .= "    }\n";
        $code .= $this->_chainMockReturns();
        $code .= $this->_chainMockExpectations();
        $code .= $this->_chainThrowMethods();
        $code .= $this->_overrideMethods($methods);
        $code .= "}\n";
        return $code;
    }

    /**
     *    Creates code within a class to generate replaced
     *    methods. All methods call the _invoke() handler
     *    with the method name and the arguments in an
     *    array.
     *    @param array $methods    Additional methods.
     *    @access private
     */
    function _createHandlerCode($methods) {
        $code = '';
        $methods = array_merge($methods, $this->_reflection->getMethods());
        foreach ($methods as $method) {
            if ($this->_isConstructor($method)) {
                continue;
            }
            $mock_reflection = new SimpleReflection($this->_mock_base);
            if (in_array($method, $mock_reflection->getMethods())) {
                continue;
            }
            $code .= "    " . $this->_reflection->getSignature($method) . " {\n";
            $code .= "        \$args = func_get_args();\n";
            $code .= "        \$result = &\$this->_invoke(\"$method\", \$args);\n";
            $code .= "        return \$result;\n";
            $code .= "    }\n";
        }
        return $code;
    }

    /**
     *    Creates code within a class to generate a new
     *    methods. All methods call the _invoke() handler
     *    on the internal mock with the method name and
     *    the arguments in an array.
     *    @param array $methods    Additional methods.
     *    @access private
     */
    function _createNewMethodCode($methods) {
        $code = '';
        foreach ($methods as $method) {
            if ($this->_isConstructor($method)) {
                continue;
            }
            $mock_reflection = new SimpleReflection($this->_mock_base);
            if (in_array($method, $mock_reflection->getMethods())) {
                continue;
            }
            $code .= "    " . $this->_reflection->getSignature($method) . " {\n";
            $code .= "        \$args = func_get_args();\n";
            $code .= "        \$result = &\$this->_mock->_invoke(\"$method\", \$args);\n";
            $code .= "        return \$result;\n";
            $code .= "    }\n";
        }
        return $code;
    }

    /**
     *    Tests to see if a special PHP method is about to
     *    be stubbed by mistake.
     *    @param string $method    Method name.
     *    @return boolean          True if special.
     *    @access private
     */
    function _isConstructor($method) {
        return in_array(
                strtolower($method),
                array('__construct', '__destruct'));
    }

    /**
     *    Creates a list of mocked methods for error checking.
     *    @param array $methods       Mocked methods.
     *    @return string              Code for a method list.
     *    @access private
     */
    function _addMethodList($methods) {
        return "    var \$_mocked_methods = array('" .
                implode("', '", array_map('strtolower', $methods)) .
                "');\n";
    }

    /**
     *    Creates code to abandon the expectation if not mocked.
     *    @param string $alias       Parameter name of method name.
     *    @return string             Code for bail out.
     *    @access private
     */
    function _bailOutIfNotMocked($alias) {
        $code  = "        if (! in_array(strtolower($alias), \$this->_mocked_methods)) {\n";
        $code .= "            trigger_error(\"Method [$alias] is not mocked\");\n";
        $code .= "            \$null = null;\n";
        $code .= "            return \$null;\n";
        $code .= "        }\n";
        return $code;
    }

    /**
     *    Creates source code for chaining to the composited
     *    mock object.
     *    @return string           Code for mock set up.
     *    @access private
     */
    function _chainMockReturns() {
        $code  = "    function setReturnValue(\$method, \$value, \$args = false) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->setReturnValue(\$method, \$value, \$args);\n";
        $code .= "    }\n";
        $code .= "    function setReturnValueAt(\$timing, \$method, \$value, \$args = false) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->setReturnValueAt(\$timing, \$method, \$value, \$args);\n";
        $code .= "    }\n";
        $code .= "    function setReturnReference(\$method, &\$ref, \$args = false) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->setReturnReference(\$method, \$ref, \$args);\n";
        $code .= "    }\n";
        $code .= "    function setReturnReferenceAt(\$timing, \$method, &\$ref, \$args = false) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->setReturnReferenceAt(\$timing, \$method, \$ref, \$args);\n";
        $code .= "    }\n";
        return $code;
    }

    /**
     *    Creates source code for chaining to an aggregated
     *    mock object.
     *    @return string                 Code for expectations.
     *    @access private
     */
    function _chainMockExpectations() {
        $code  = "    function expect(\$method, \$args = false, \$msg = '%s') {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expect(\$method, \$args, \$msg);\n";
        $code .= "    }\n";
        $code .= "    function expectArguments(\$method, \$args = false, \$msg = '%s') {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expectArguments(\$method, \$args, \$msg);\n";
        $code .= "    }\n";
        $code .= "    function expectAt(\$timing, \$method, \$args = false, \$msg = '%s') {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args, \$msg);\n";
        $code .= "    }\n";
        $code .= "    function expectArgumentsAt(\$timing, \$method, \$args = false, \$msg = '%s') {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args, \$msg);\n";
        $code .= "    }\n";
        $code .= "    function expectCallCount(\$method, \$count) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expectCallCount(\$method, \$count, \$msg = '%s');\n";
        $code .= "    }\n";
        $code .= "    function expectMaximumCallCount(\$method, \$count, \$msg = '%s') {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expectMaximumCallCount(\$method, \$count, \$msg = '%s');\n";
        $code .= "    }\n";
        $code .= "    function expectMinimumCallCount(\$method, \$count, \$msg = '%s') {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expectMinimumCallCount(\$method, \$count, \$msg = '%s');\n";
        $code .= "    }\n";
        $code .= "    function expectNever(\$method) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expectNever(\$method);\n";
        $code .= "    }\n";
        $code .= "    function expectOnce(\$method, \$args = false, \$msg = '%s') {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expectOnce(\$method, \$args, \$msg);\n";
        $code .= "    }\n";
        $code .= "    function expectAtLeastOnce(\$method, \$args = false, \$msg = '%s') {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->expectAtLeastOnce(\$method, \$args, \$msg);\n";
        $code .= "    }\n";
        $code .= "    function tally() {\n";
        $code .= "    }\n";
        return $code;
    }
    
    /**
     *    Adds code for chaining the throw methods.
     *    @return string           Code for chains.
     *    @access private
     */
    function _chainThrowMethods() {
        $code  = "    function throwOn(\$method, \$exception = false, \$args = false) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->throwOn(\$method, \$exception, \$args);\n";
        $code .= "    }\n";
        $code .= "    function throwAt(\$timing, \$method, \$exception = false, \$args = false) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->throwAt(\$timing, \$method, \$exception, \$args);\n";
        $code .= "    }\n";
        $code .= "    function errorOn(\$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->errorOn(\$method, \$error, \$args, \$severity);\n";
        $code .= "    }\n";
        $code .= "    function errorAt(\$timing, \$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n";
        $code .= $this->_bailOutIfNotMocked("\$method");
        $code .= "        \$this->_mock->errorAt(\$timing, \$method, \$error, \$args, \$severity);\n";
        $code .= "    }\n";
        return $code;
    }

    /**
     *    Creates source code to override a list of methods
     *    with mock versions.
     *    @param array $methods    Methods to be overridden
     *                             with mock versions.
     *    @return string           Code for overridden chains.
     *    @access private
     */
    function _overrideMethods($methods) {
        $code = "";
        foreach ($methods as $method) {
            if ($this->_isConstructor($method)) {
                continue;
            }
            $code .= "    " . $this->_reflection->getSignature($method) . " {\n";
            $code .= "        \$args = func_get_args();\n";
            $code .= "        \$result = &\$this->_mock->_invoke(\"$method\", \$args);\n";
            $code .= "        return \$result;\n";
            $code .= "    }\n";
        }
        return $code;
    }
}
?>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?