PHPUnit:从抽象 class 构造函数调用子方法
PHPUnit: Calling a child method from abstract class constructor
我发现 PHPUnit 出现了(对我而言)意外行为,这是一个错误,还是我做错了什么?
简化测试用例:
abstract class abstractSpeaker {
public function __construct($param) {
$this->setSpeaker($param);
$this->getSpeaker()->speak(); //bad line, causes error
$this->tellSpeakerToSpeak(); //this lines ok
}
abstract function setSpeaker($value);
abstract function getSpeaker();
function tellSpeakerToSpeak() {
$this->getSpeaker()->speak(); //this line works, but is same as bad line
}
}
class speaker extends abstractSpeaker {
protected $speaker;
function setSpeaker($value) {
$this->speaker = $value;
}
function getSpeaker() {
return $this->speaker;
}
}
class SayHello {
public function speak() { print "hello"; }
}
class abstractTest extends \PHPUnit_Framework_TestCase {
public function testIndex() {
$mock = $this->getMockBuilder(speaker::class)
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMock();
$mock->tellSpeakerToSpeak();
}
}
如果我在 PHPUnit 中 运行 上面的代码,我得到以下错误:
Fatal error: Call to a member function speak() on a non-object in abstracttest.php on line 9
将您的模拟电话更改为:
$mock = $this->getMockBuilder('speaker')
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMock();
尽管这是一个奇怪的测试示例。通常,我们不想创建我们正在测试的 class 的模拟。创建一个 object 传递给我们正在嘲笑的 class 对我来说有点倒退。
您调用了测试用例 abstractTest
,但您正在测试抽象 class 的具体 child。如果您的目的是测试 abstractSpeaker
那么您可以像这样使用 getMockForAbstractClass
:
$mock = $this->getMockBuilder('abstractSpeaker')
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMockForAbstractClass();
然而,这引入了一个问题,即 setSpeaker
是您在构造函数中调用的抽象方法,我们无法模拟它。就个人而言,我认为您在示例构造函数中做的太多了,会删除它。最终会像这样:
public function testIndex() {
$mock = $this->getMockBuilder('abstractSpeaker')
->setMethods(['setSpeaker', 'getSpeaker'])
->getMockForAbstractClass();
$mockSpeaker = $this->getMockBuilder('SayHello')
->setMethods(['speak'])
->getMock();
$mockSpeaker->expects($this->once())
->method('speak')
->will($this->returnCallback(function() { print 'Hello' }));
$mock->expects($this->once())
->method('setSpeaker')
->with($mockSpeaker);
$mock->expects($this->once())
->method('getSpeaker')
->willReturn($mockSpeaker);
$mock->tellSpeakerToSpeak($mockSpeaker);
$this->expectOutputString('Hello');
}
虽然在这种情况下,确实完全没有必要使用setters和getters。您可以将扬声器传递给方法并直接调用 speak
方法。
如果您打算测试具体的 child,您可以这样做:
public function testIndex() {
$mock = $this->getMockBuilder('SayHello')
->setMethods(['speak'])
->getMock();
$mock->expects($this->once())
->method('speak')
->will($this->returnCallback(function() { print 'Hello' }));
$sut = new speaker($mock);
$this->expectOutputString('Hello');
}
我发现 PHPUnit 出现了(对我而言)意外行为,这是一个错误,还是我做错了什么? 简化测试用例:
abstract class abstractSpeaker {
public function __construct($param) {
$this->setSpeaker($param);
$this->getSpeaker()->speak(); //bad line, causes error
$this->tellSpeakerToSpeak(); //this lines ok
}
abstract function setSpeaker($value);
abstract function getSpeaker();
function tellSpeakerToSpeak() {
$this->getSpeaker()->speak(); //this line works, but is same as bad line
}
}
class speaker extends abstractSpeaker {
protected $speaker;
function setSpeaker($value) {
$this->speaker = $value;
}
function getSpeaker() {
return $this->speaker;
}
}
class SayHello {
public function speak() { print "hello"; }
}
class abstractTest extends \PHPUnit_Framework_TestCase {
public function testIndex() {
$mock = $this->getMockBuilder(speaker::class)
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMock();
$mock->tellSpeakerToSpeak();
}
}
如果我在 PHPUnit 中 运行 上面的代码,我得到以下错误:
Fatal error: Call to a member function speak() on a non-object in abstracttest.php on line 9
将您的模拟电话更改为:
$mock = $this->getMockBuilder('speaker')
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMock();
尽管这是一个奇怪的测试示例。通常,我们不想创建我们正在测试的 class 的模拟。创建一个 object 传递给我们正在嘲笑的 class 对我来说有点倒退。
您调用了测试用例 abstractTest
,但您正在测试抽象 class 的具体 child。如果您的目的是测试 abstractSpeaker
那么您可以像这样使用 getMockForAbstractClass
:
$mock = $this->getMockBuilder('abstractSpeaker')
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMockForAbstractClass();
然而,这引入了一个问题,即 setSpeaker
是您在构造函数中调用的抽象方法,我们无法模拟它。就个人而言,我认为您在示例构造函数中做的太多了,会删除它。最终会像这样:
public function testIndex() {
$mock = $this->getMockBuilder('abstractSpeaker')
->setMethods(['setSpeaker', 'getSpeaker'])
->getMockForAbstractClass();
$mockSpeaker = $this->getMockBuilder('SayHello')
->setMethods(['speak'])
->getMock();
$mockSpeaker->expects($this->once())
->method('speak')
->will($this->returnCallback(function() { print 'Hello' }));
$mock->expects($this->once())
->method('setSpeaker')
->with($mockSpeaker);
$mock->expects($this->once())
->method('getSpeaker')
->willReturn($mockSpeaker);
$mock->tellSpeakerToSpeak($mockSpeaker);
$this->expectOutputString('Hello');
}
虽然在这种情况下,确实完全没有必要使用setters和getters。您可以将扬声器传递给方法并直接调用 speak
方法。
如果您打算测试具体的 child,您可以这样做:
public function testIndex() {
$mock = $this->getMockBuilder('SayHello')
->setMethods(['speak'])
->getMock();
$mock->expects($this->once())
->method('speak')
->will($this->returnCallback(function() { print 'Hello' }));
$sut = new speaker($mock);
$this->expectOutputString('Hello');
}