如何模拟非确定性方法?
How to mock a non-deterministic method?
使用非确定性方法考虑 class:
class Foo
{
public function getSome(): int
{
static $int = 0;
return ++$int;
}
}
如何使用 Mockery 模拟相同的行为?
class Bar
{
public function useFoo(Foo $foo)
{
echo $foo->getSome() . ", ";
echo $foo->getSome() . ", ";
echo $foo->getSome();
}
}
$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('getSome')->andReturn(1);
$mock->shouldReceive('getSome')->andReturn(2);
$mock->shouldReceive('getSome')->andReturn(3);
// Should display 1, 2, 3
$bar = new Bar();
$bar->useFoo($mock);
另一个例子:
class Clock
{
public function getTime(): int
{
return time();
}
}
class Sleeper
{
public function sleep(int $seconds): void
{
sleep($seconds);
}
}
如何使用 Mockery 模拟相同的行为?
class Stoper
{
private Clock $clock;
private Sleeper $sleeper;
public function __construct(Clock $clock, Sleeper $sleeper)
{
$this->clock = $clock;
$this->sleeper = $sleeper;
}
public function measure(int $seconds): int
{
$start = $this->clock->getTime();
$this->sleeper->sleep($seconds);
return $this->clock->getTime();
}
}
$clockMock = Mockery::mock(Clock::class);
$clockMock->shouldReceive('getTime')->andReturn(1000000001);
$clockMock->shouldReceive('getTime')->andReturn(1000000011);
$sleeperMock = Mockery::mock(Sleeper::class);
$sleeperMock->shouldReceive('sleep');
$stoper = new Stoper($clockMock, $sleeperMock);
$this->assertEquals(10, $stoper->measure(10));
肮脏的解决方案
我发现我可以耍花招了
class ClockCaller
{
public function getTime(int $callNumber)
{
return (new Clock())->getTime();
}
}
class Stoper
{
private ClockCaller $clockCaller;
private Sleeper $sleeper;
public function __construct(ClockCaller $clockCaller, Sleeper $sleeper)
{
$this->clockCaller = $clockCaller;
$this->sleeper = $sleeper;
}
public function measure(int $seconds): int
{
$start = $this->clockCaller->getTime(1);
$this->sleeper->sleep($seconds);
return $this->clockCaller->getTime(2);
}
}
$clockMock = Mockery::mock(ClockCaller::class);
$clockMock->shouldReceive('getTime')->withArgs([1])->andReturn(1000000001);
$clockMock->shouldReceive('getTime')->withArgs([2])->andReturn(1000000011);
不过好像很讨厌
Mockery 文档中的 Simple Example 给出了您问题的答案。应该 return 连续 10、12 和 14 度的温度服务可以这样模拟:
$service = Mockery::mock('service');
$service->shouldReceive('readTemp')
->times(3)
->andReturn(10, 12, 14);
因此,对于您的 Foo
class:
$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('getSome')
->times(3)
->andReturn(1, 2, 3);
还有你的Clock
:
$clockMock = Mockery::mock(Clock::class);
$clockMock->shouldReceive('getTime')
->times(2)
->andReturn(1000000001, 1000000011);
您可以使用 expectOutputString 检查输出:
public function testFoo(){
$this->expectOutputString("foo");
echo "foo";
}
关于睡眠功能,你可以使用\sleep()
(全局命名空间),并覆盖它..
另外,您可以使用 mocking built-in PHP functions
“如何模拟 non-deterministic 方法?”问题很笼统。
必须分别考虑每种方法。
第一个例子闻起来像上面的sayyid,划掉。
我们来看第二个例子
问题是时间不可预测,需要修复。
interface ClockInterface
{
public function getTime(): int;
}
class Clock implements ClockInterface
{
public function getTime(): int
{
return time();
}
}
class ClockTest implements ClockInterface
{
public function getTime(): int
{
return 0; // first unix ts
}
}
class Stoper
{
private ClockInterface $clock;
private Sleeper $sleeper;
public function __construct(ClockInterface $clock, Sleeper $sleeper)
{
$this->clock = $clock;
$this->sleeper = $sleeper;
}
...
}
单元测试:将 ClockTest
class 作为 Stoper
class 的第一个构造参数注入并解决问题。
如果创建包,ClockTest
class 可以是 dist
部分,这是一个很好的做法 (https://github.com/Seldaek/monolog/blob/main/src/Monolog/Test/TestCase.php)
使用非确定性方法考虑 class:
class Foo
{
public function getSome(): int
{
static $int = 0;
return ++$int;
}
}
如何使用 Mockery 模拟相同的行为?
class Bar
{
public function useFoo(Foo $foo)
{
echo $foo->getSome() . ", ";
echo $foo->getSome() . ", ";
echo $foo->getSome();
}
}
$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('getSome')->andReturn(1);
$mock->shouldReceive('getSome')->andReturn(2);
$mock->shouldReceive('getSome')->andReturn(3);
// Should display 1, 2, 3
$bar = new Bar();
$bar->useFoo($mock);
另一个例子:
class Clock
{
public function getTime(): int
{
return time();
}
}
class Sleeper
{
public function sleep(int $seconds): void
{
sleep($seconds);
}
}
如何使用 Mockery 模拟相同的行为?
class Stoper
{
private Clock $clock;
private Sleeper $sleeper;
public function __construct(Clock $clock, Sleeper $sleeper)
{
$this->clock = $clock;
$this->sleeper = $sleeper;
}
public function measure(int $seconds): int
{
$start = $this->clock->getTime();
$this->sleeper->sleep($seconds);
return $this->clock->getTime();
}
}
$clockMock = Mockery::mock(Clock::class);
$clockMock->shouldReceive('getTime')->andReturn(1000000001);
$clockMock->shouldReceive('getTime')->andReturn(1000000011);
$sleeperMock = Mockery::mock(Sleeper::class);
$sleeperMock->shouldReceive('sleep');
$stoper = new Stoper($clockMock, $sleeperMock);
$this->assertEquals(10, $stoper->measure(10));
肮脏的解决方案
我发现我可以耍花招了
class ClockCaller
{
public function getTime(int $callNumber)
{
return (new Clock())->getTime();
}
}
class Stoper
{
private ClockCaller $clockCaller;
private Sleeper $sleeper;
public function __construct(ClockCaller $clockCaller, Sleeper $sleeper)
{
$this->clockCaller = $clockCaller;
$this->sleeper = $sleeper;
}
public function measure(int $seconds): int
{
$start = $this->clockCaller->getTime(1);
$this->sleeper->sleep($seconds);
return $this->clockCaller->getTime(2);
}
}
$clockMock = Mockery::mock(ClockCaller::class);
$clockMock->shouldReceive('getTime')->withArgs([1])->andReturn(1000000001);
$clockMock->shouldReceive('getTime')->withArgs([2])->andReturn(1000000011);
不过好像很讨厌
Mockery 文档中的 Simple Example 给出了您问题的答案。应该 return 连续 10、12 和 14 度的温度服务可以这样模拟:
$service = Mockery::mock('service');
$service->shouldReceive('readTemp')
->times(3)
->andReturn(10, 12, 14);
因此,对于您的 Foo
class:
$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('getSome')
->times(3)
->andReturn(1, 2, 3);
还有你的Clock
:
$clockMock = Mockery::mock(Clock::class);
$clockMock->shouldReceive('getTime')
->times(2)
->andReturn(1000000001, 1000000011);
您可以使用 expectOutputString 检查输出:
public function testFoo(){
$this->expectOutputString("foo");
echo "foo";
}
关于睡眠功能,你可以使用\sleep()
(全局命名空间),并覆盖它..
另外,您可以使用 mocking built-in PHP functions
“如何模拟 non-deterministic 方法?”问题很笼统。
必须分别考虑每种方法。
第一个例子闻起来像上面的sayyid,划掉。
我们来看第二个例子
问题是时间不可预测,需要修复。
interface ClockInterface
{
public function getTime(): int;
}
class Clock implements ClockInterface
{
public function getTime(): int
{
return time();
}
}
class ClockTest implements ClockInterface
{
public function getTime(): int
{
return 0; // first unix ts
}
}
class Stoper
{
private ClockInterface $clock;
private Sleeper $sleeper;
public function __construct(ClockInterface $clock, Sleeper $sleeper)
{
$this->clock = $clock;
$this->sleeper = $sleeper;
}
...
}
单元测试:将 ClockTest
class 作为 Stoper
class 的第一个构造参数注入并解决问题。
如果创建包,ClockTest
class 可以是 dist
部分,这是一个很好的做法 (https://github.com/Seldaek/monolog/blob/main/src/Monolog/Test/TestCase.php)