Вступ
Гаразд, я бачу, що для насмішки є одне рішення, тому, як мені не подобається насмішка, я збираюся дати вам альтернативу пророцтва, але я б запропонував вам спочатку прочитати про різницю між насмішкою та пророцтвом.
Довга коротка історія : "Пророцтво використовує підхід, який називається прив'язкою повідомлень - це означає, що поведінка методу не змінюється з часом, а скоріше змінюється іншим методом".
Реальний проблемний код, який слід висвітлити
class Processor
{
/**
* @var MutatorResolver
*/
private $mutatorResolver;
/**
* @var ChunksStorage
*/
private $chunksStorage;
/**
* @param MutatorResolver $mutatorResolver
* @param ChunksStorage $chunksStorage
*/
public function __construct(MutatorResolver $mutatorResolver, ChunksStorage $chunksStorage)
{
$this->mutatorResolver = $mutatorResolver;
$this->chunksStorage = $chunksStorage;
}
/**
* @param Chunk $chunk
*
* @return bool
*/
public function process(Chunk $chunk): bool
{
$mutator = $this->mutatorResolver->resolve($chunk);
try {
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
$mutator->mutate($chunk);
$chunk->processingAccepted();
$this->chunksStorage->updateChunk($chunk);
}
catch (UnableToMutateChunkException $exception) {
$chunk->processingRejected();
$this->chunksStorage->updateChunk($chunk);
// Log the exception, maybe together with Chunk insert them into PostProcessing Queue
}
return false;
}
}
Рішення пророцтва PhpUnit
class ProcessorTest extends ChunkTestCase
{
/**
* @var Processor
*/
private $processor;
/**
* @var MutatorResolver|ObjectProphecy
*/
private $mutatorResolverProphecy;
/**
* @var ChunksStorage|ObjectProphecy
*/
private $chunkStorage;
public function setUp()
{
$this->mutatorResolverProphecy = $this->prophesize(MutatorResolver::class);
$this->chunkStorage = $this->prophesize(ChunksStorage::class);
$this->processor = new Processor(
$this->mutatorResolverProphecy->reveal(),
$this->chunkStorage->reveal()
);
}
public function testProcessShouldPersistChunkInCorrectStatusBeforeAndAfterTheMutateOperation()
{
$self = $this;
// Chunk is always passed with ACK_BY_QUEUE status to process()
$chunk = $this->createChunk();
$chunk->ackByQueue();
$campaignMutatorMock = $self->prophesize(CampaignMutator::class);
$campaignMutatorMock
->mutate($chunk)
->shouldBeCalled();
$this->mutatorResolverProphecy
->resolve($chunk)
->shouldBeCalled()
->willReturn($campaignMutatorMock->reveal());
$this->chunkStorage
->updateChunk($chunk)
->shouldBeCalled()
->will(
function($args) use ($self) {
$chunk = $args[0];
$self->assertTrue($chunk->status() === Chunk::STATUS_PROCESSING_IN_PROGRESS);
$self->chunkStorage
->updateChunk($chunk)
->shouldBeCalled()
->will(
function($args) use ($self) {
$chunk = $args[0];
$self->assertTrue($chunk->status() === Chunk::STATUS_PROCESSING_UPLOAD_ACCEPTED);
return true;
}
);
return true;
}
);
$this->processor->process($chunk);
}
}
Підсумок
Ще раз, Пророцтво є приголомшливішим! Моя хитрість полягає в тому, щоб використовувати характер обміну повідомленнями пророцтва, і хоча це, на жаль, схоже на типовий пекельний код JavaScript у зворотному дзвінку, починаючи з $ self = $ this; так як вам дуже рідко доводиться писати одиничні тести на кшталт цього, я думаю, що це приємне рішення, і це, безумовно, легко прослідкувати, налагодження, як це фактично описує виконання програми.
BTW: Є друга альтернатива, але вимагає змінити код, який ми тестуємо. Ми можемо перетворити неполадок і перенести їх в окремий клас:
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
можна обернути як:
$processorChunkStorage->persistChunkToInProgress($chunk);
і це все, але оскільки я не хотів створювати для нього ще один клас, я віддаю перевагу першому.