测试抽象类

159

如何使用PHPUnit测试抽象类的具体方法?

我希望在测试中能够创建一些对象,但是我不知道最佳实践是什么,也不确定PHPUnit是否允许这样做。


10
或许你应该考虑更改被接受的答案。 - Jacob
1
也许这个链接可以帮到你:https://dev59.com/-nVC5IYBdhLWcg3wnCSA#2947823。 - Nigel Thorne
6个回答

263

对于抽象类的单元测试,并不一定意味着测试接口,因为抽象类可以有具体方法,这些具体方法可以被测试。

在编写某些库代码时,拥有某些基类并期望在应用层中进行扩展并不少见。如果您想确保库代码得到测试,您需要手段来测试抽象类的具体方法。

个人而言,我使用PHPUnit,并且它有所谓的存根(stubs)和模拟对象(mock objects)来帮助您测试这种情况。

直接引用自 PHPUnit 手册:

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

    public abstract function abstractMethod();
}

class AbstractClassTest extends PHPUnit_Framework_TestCase
{
    public function testConcreteMethod()
    {
        $stub = $this->getMockForAbstractClass('AbstractClass');
        $stub->expects($this->any())
             ->method('abstractMethod')
             ->will($this->returnValue(TRUE));

        $this->assertTrue($stub->concreteMethod());
    }
}

Mock对象给您以下几个优点:

  • 您不需要具体实现抽象类,只需使用存根即可。
  • 您可以调用具体方法并断言它们是否执行正确。
  • 如果具体方法依赖于未实现(抽象)的方法,则可以使用will() PHPUnit方法存根返回值。

53

需要注意的是,从PHP 7开始,增加了对匿名类的支持。这为您设置抽象类测试提供了另一种途径,这不依赖于PHPUnit特定功能。

class AbstractClassTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var AbstractClass
     */
    private $testedClass;

    public function setUp()
    {
        $this->testedClass = new class extends AbstractClass {

            protected function abstractMethod()
            {
                // Put a barebones implementation here
            }
        };
    }

    // Put your tests here
}

4
谢谢你!在PHPUnit中使用匿名类给我创建各种测试提供了很大的灵活性。 - Alice Wonder

42

这是一个很好的问题,我也一直在寻找答案。
幸运的是,PHPUnit 已经为这种情况提供了getMockForAbstractClass()方法,例如:

protected function setUp()
{
    $stub = $this->getMockForAbstractClass('Some_Abstract_Class');
    $this->_object = $stub;
}

重要提示:

请注意,这需要 PHPUnit > 3.5.4。在之前的版本中存在一个错误

升级到最新版本:

sudo pear channel-update pear.phpunit.de
sudo pear upgrade phpunit/PHPUnit

听起来很有趣,但你会针对模拟进行测试吗?测试会是什么样子的?例如:在测试用例中扩展模拟并针对扩展的测试类进行测试? - Stephane Gosselin

1

Nelson的答案是错误的。

抽象类不需要所有方法都是抽象的。

我们需要测试的是已实现的方法。

你可以在单元测试文件中创建一个假存根类,让它扩展抽象类,并仅实现所需的内容而没有任何功能,并进行测试。

干杯。


1
Eran,你的方法应该是可行的,但它违背了先编写测试再编写实际代码的趋势。
我建议的做法是,在所涉及的抽象类的非抽象子类上编写所需功能的测试,然后编写抽象类和实现子类,最后运行测试。
你的测试显然应该测试抽象类的定义方法,但始终通过子类进行。

我认为这是一个随意的答案: 您有一个抽象类“A”,具有普通的“foo()”方法。 这个“foo()”方法被所有'B'和'C'类共同使用,它们都派生自'A'。 您会选择哪个类来测试“foo()”? - user3790897

0

如果您不想为了在已经实现了抽象类的方法上执行单元测试而子类化抽象类,您可以尝试查看您的框架是否允许您模拟抽象类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接