使用phpunit在抽象类中模拟具体方法

使用phpunit在抽象类中模拟具体方法,php,unit-testing,mocking,phpunit,Php,Unit Testing,Mocking,Phpunit,使用PHPUnit在抽象类中模拟具体方法有什么好方法吗 到目前为止,我发现: expects()->will()使用抽象方法可以正常工作 它不适用于具体方法。而是运行原始方法 使用mockbuilder并将所有的抽象方法和具体方法赋予setMethods()是可行的。但是,它要求您指定所有抽象方法,这使得测试变得脆弱且过于冗长 MockBuilder::getMockForAbstractClass()忽略setMethod() 以下是一些单元测试,举例说明上述几点: abstract c

使用PHPUnit在抽象类中模拟具体方法有什么好方法吗

到目前为止,我发现:

  • expects()->will()使用抽象方法可以正常工作
  • 它不适用于具体方法。而是运行原始方法
  • 使用mockbuilder并将所有的抽象方法和具体方法赋予setMethods()是可行的。但是,它要求您指定所有抽象方法,这使得测试变得脆弱且过于冗长
  • MockBuilder::getMockForAbstractClass()忽略setMethod()

以下是一些单元测试,举例说明上述几点:

abstract class AbstractClass {
    public function concreteMethod() {
        return $this->abstractMethod();
    }

    public abstract function abstractMethod();
}


class AbstractClassTest extends PHPUnit_Framework_TestCase {
    /**
     * This works for abstract methods.
     */
    public function testAbstractMethod() {
        $stub = $this->getMockForAbstractClass('AbstractClass');
        $stub->expects($this->any())
                ->method('abstractMethod')
                ->will($this->returnValue(2));

        $this->assertSame(2, $stub->concreteMethod()); // Succeeds
    }

    /**
     * Ideally, I would like this to work for concrete methods too.
     */
    public function testConcreteMethod() {
        $stub = $this->getMockForAbstractClass('AbstractClass');
        $stub->expects($this->any())
                ->method('concreteMethod')
                ->will($this->returnValue(2));

        $this->assertSame(2, $stub->concreteMethod()); // Fails, concreteMethod returns NULL
    }

    /**
     * One way to mock the concrete method, is to use the mock builder,
     * and set the methods to mock.
     *
     * The downside of doing it this way, is that all abstract methods
     * must be specified in the setMethods() call. If you add a new abstract
     * method, all your existing unit tests will fail.
     */
    public function testConcreteMethod__mockBuilder_getMock() {
        $stub = $this->getMockBuilder('AbstractClass')
                ->setMethods(array('concreteMethod', 'abstractMethod'))
                ->getMock();
        $stub->expects($this->any())
                ->method('concreteMethod')
                ->will($this->returnValue(2));

        $this->assertSame(2, $stub->concreteMethod()); // Succeeds
    }

    /**
     * Similar to above, but using getMockForAbstractClass().
     * Apparently, setMethods() is ignored by getMockForAbstractClass()
     */
    public function testConcreteMethod__mockBuilder_getMockForAbstractClass() {
        $stub = $this->getMockBuilder('AbstractClass')
                ->setMethods(array('concreteMethod'))
                ->getMockForAbstractClass();
        $stub->expects($this->any())
                ->method('concreteMethod')
                ->will($this->returnValue(2));

        $this->assertSame(2, $stub->concreteMethod()); // Fails, concreteMethod returns NULL
    }
}
我在基本测试用例中重写了
getMock()
,以添加所有抽象方法,因为您无论如何都必须模拟它们。毫无疑问,你可以用建筑商做类似的事情

重要提示:不能模拟私有方法

public function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE) {
    if ($methods !== null) {
        $methods = array_unique(array_merge($methods, 
                self::getAbstractMethods($originalClassName, $callAutoload)));
    }
    return parent::getMock($originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload);
}

/**
 * Returns an array containing the names of the abstract methods in <code>$class</code>.
 *
 * @param string $class name of the class
 * @return array zero or more abstract methods names
 */
public static function getAbstractMethods($class, $autoload=true) {
    $methods = array();
    if (class_exists($class, $autoload) || interface_exists($class, $autoload)) {
        $reflector = new ReflectionClass($class);
        foreach ($reflector->getMethods() as $method) {
            if ($method->isAbstract()) {
                $methods[] = $method->getName();
            }
        }
    }
    return $methods;
}

两年前有一个拉取请求,但文档中从未添加该信息:

可以在getMockForAbstractClass()的参数7中的数组中传递具体方法


请参阅代码:

您不需要测试抽象类,因为它们是抽象的。或者您想同时编写一个抽象测试用例吗?抽象类是另一个类的依赖项。所以我想测试使用$object->concreteMethod()的SomeClass::getMyCalculatedValue()。由于concreteMethod()可以更改,或者可能很难设置,因此我想指定concreteMethod()的返回值。这与
getMockForAbstractClass
有何不同?@whoolishseth PHPUnit的早期版本不允许您这样做。由于在不模拟每个抽象方法的情况下无法实例化模拟对象,因此在调用
getMock()
等时将它们合并到列表中似乎是合理的。