【问题标题】:How to test a concrete method calling an abstract method with phpunit如何使用phpunit测试调用抽象方法的具体方法
【发布时间】:2015-03-20 05:13:25
【问题描述】:

我有一个带有具体方法的抽象类。所以我想测试那些具体的方法。

这是我的抽象类:

abstract class File {
    private $debug_filename_pattern = 'DELETE_ME_%s.debug';
    private $filename;
    private $filepath;
    
    abstract public function buildFilename();
    
    public function __construct($debug = false) {
        $filename = $this->buildFilename();
        if ($debug) {
            $filename = sprintf($this->debug_filename_pattern, $filename);
        }
        $this->filename = $filename;
        $this->buildFilepath();
    }
    
    private function buildFilepath() {
        $this->filepath = ini_get('upload_tmp_dir') . DIRECTORY_SEPARATOR . $this->filename;
    }
}

我阅读了phpunit documentation 中关于测试抽象类的部分,我想出了那个测试:

final class FileTest extends \PHPUnit_Framework_TestCase {
    public function test() {
        $stub = $this->getMockForAbstractClass('MyBundle\File', [true]);
        $stub->expects($this->atLeastOnce())
                ->method('buildFilename')
                ->withAnyParameters()
                ->will($this->returnValue('test.log'));
        $this->assertEquals('C:\xampp\tmp\DELETE_ME_test.log.debug', $stub->getFilePath());
    }
}

但它不起作用。我的断言总是返回它失败并显示以下错误消息:

Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'C:\xampp\tmp\DELETE_ME_test.log.debug'
+'C:\xampp\tmp\DELETE_ME_.debug'

我知道我的模拟对象已实例化,然后我为 buildFilename 方法添加了一个模拟。让我的测试总是失败。

有没有办法在实例化之前模拟我的抽象方法?我应该重构我的抽象类吗?

【问题讨论】:

  • 根据您的需要,过去我只是在我的测试代码中为抽象类创建一个类,然后在该类中测试具体方法。
  • 问题是没有测试具体的方法。问题是构造函数调用了一个抽象方法,它的模拟是在实例化之后定义的。这使得该方法总是返回 null。

标签: php unit-testing phpunit abstract-class


【解决方案1】:

我认为您无法按照自己的方式设置模拟。当您->getMock() 时,正在调用构造方法。然后,您会尝试在事后设定期望。

一般来说,我发现当某些东西变得难以测试时,就像在这种情况下,这表明设计存在问题。我认为您遇到的问题是在这种情况下您在构造函数中做的太多了。

您正在做各种繁重的工作来确定对象构造的文件路径。为什么不更改它,以便在您致电 getFilePath 时发生。你的班级最终会是这样的:

abstract class File {

    private $debug_filename_pattern = 'DELETE_ME_%s.debug';
    private $filename;
    private $filepath;
    protected $debug;

    abstract public function buildFilename();

    public function __construct($debug = false) {
        $this->debug = $debug;
    }

    private function buildFilepath() {
        $filename = $this->buildFilename();
        if ($this->debug) {
            $filename = sprintf($this->debug_filename_pattern, $filename);
        }
        $this->filename = $filename;
        $this->filepath = ini_get('upload_tmp_dir') . DIRECTORY_SEPARATOR . $this->filename;
    }

    public function getFilePath() {
        if(!this->filepath) {
            $this->buildFilepath();
        }

        return $this->filepath;
    }
}

现在在您的测试中,以确保只构建一次路径,只需再添加一次断言即可。

final class FileTest extends \PHPUnit_Framework_TestCase {

    public function test() {
        $stub = $this->getMockForAbstractClass('MyBundle\File', [true]);
        $stub->expects($this->once())
                ->method('buildFilename')
                ->withAnyParameters()
                ->will($this->returnValue('test.log'));
        $this->assertEquals('C:\xampp\tmp\DELETE_ME_test.log.debug', $stub->getFilePath());
        $this->assertEquals('C:\xampp\tmp\DELETE_ME_test.log.debug', $stub->getFilePath());
    }

}

【讨论】:

  • 期望应该是 $this->once()
  • @crowebird 谢谢。修复了测试。
  • 我最终构建了与您描述的非常相似的东西。感谢您的洞察力。
猜你喜欢
  • 2011-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多