php - videoconferencias - videollamada grupal skype android
phpunit simula múltiples llamadas múltiples con diferentes argumentos (5)
¿Hay alguna manera de definir diferentes mock-expects para diferentes argumentos de entrada? Por ejemplo, tengo una clase de capa de base de datos llamada DB. Esta clase tiene el método llamado "Query (string $ query)", ese método toma una cadena de consulta SQL en la entrada. ¿Puedo crear simulacros para esta clase (DB) y establecer diferentes valores de retorno para diferentes llamadas al método Query que dependen de la cadena de consulta de entrada?
Introducción
De acuerdo, veo que hay una solución para Mockery, así que como no me gusta Mockery, les voy a dar una alternativa de Profecía, pero les sugiero primero que lean sobre la diferencia entre Mockery y Prophecy primero.
Para resumir : "La profecía usa un enfoque llamado encuadernación de mensaje : significa que el comportamiento del método no cambia con el tiempo, sino que es cambiado por el otro método".
Código problemático del mundo real para cubrir
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 Prophecy solution
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);
}
}
Resumen
¡Una vez más, Prophecy es más increíble! Mi truco es aprovechar la naturaleza vinculante de los mensajes de Prophecy y, a pesar de que, tristemente, se parece a un código de infierno de javascript de devolución de llamada típico, comenzando con $ self = $ this; como rara vez tiene que escribir pruebas unitarias como esta, creo que es una buena solución y definitivamente es fácil de seguir, depurar, ya que realmente describe la ejecución del programa.
Por cierto: hay una segunda alternativa, pero requiere cambiar el código que estamos probando. Podríamos envolver a los alborotadores y moverlos a una clase separada:
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
podría ser envuelto como:
$processorChunkStorage->persistChunkToInProgress($chunk);
y eso es todo, pero como no quería crear otra clase para él, prefiero el primero.
No es ideal usar at()
si puede evitarlo porque, como afirman sus documentos
El parámetro $ index para el matcher at () se refiere al índice, que comienza en cero, en todas las invocaciones de métodos para un objeto simulado dado. Tenga cuidado al usar este matcher ya que puede llevar a pruebas frágiles que están demasiado ligadas a detalles de implementación específicos.
Desde 4.1 puede usar withConsecutive
por ejemplo.
$mock->expects($this->exactly(2))
->method(''set'')
->withConsecutive(
[$this->equalTo(''foo''), $this->greaterThan(0)],
[$this->equalTo(''bar''), $this->greaterThan(0)]
);
Si desea hacer que regrese en llamadas consecutivas:
$mock->method(''set'')
->withConsecutive([$argA1, $argA2], [$argB1], [$argC1, $argC2])
->willReturnOnConsecutiveCalls($retValueA, $retValueB, $retValueC);
Parece que Mockery ( https://github.com/padraic/mockery ) es compatible con esto. En mi caso, quiero verificar que se creen 2 índices en una base de datos:
Burla, funciona:
use Mockery as m;
//...
$coll = m::mock(MongoCollection::class);
$db = m::mock(MongoDB::class);
$db->shouldReceive(''selectCollection'')->withAnyArgs()->times(1)->andReturn($coll);
$coll->shouldReceive(''createIndex'')->times(1)->with([''foo'' => true]);
$coll->shouldReceive(''createIndex'')->times(1)->with([''bar'' => true], [''unique'' => true]);
new MyCollection($db);
PHPUnit, esto falla:
$coll = $this->getMockBuilder(MongoCollection::class)->disableOriginalConstructor()->getMock();
$db = $this->getMockBuilder(MongoDB::class)->disableOriginalConstructor()->getMock();
$db->expects($this->once())->method(''selectCollection'')->with($this->anything())->willReturn($coll);
$coll->expects($this->atLeastOnce())->method(''createIndex'')->with([''foo'' => true]);
$coll->expects($this->atLeastOnce())->method(''createIndex'')->with([''bar'' => true], [''unique'' => true]);
new MyCollection($db);
La burla también tiene una sintaxis más agradable en mi humilde opinión. Parece ser un poco más lento que PHPUnits capacidad de burlarse incorporada, pero YMMV.
Según lo que he encontrado, la mejor manera de resolver este problema es usar la funcionalidad de mapa de valores de PHPUnit.
Ejemplo de la documentación de PHPUnit :
class SomeClass {
public function doSomething() {}
}
class StubTest extends /PHPUnit_Framework_TestCase {
public function testReturnValueMapStub() {
$mock = $this->getMock(''SomeClass'');
// Create a map of arguments to return values.
$map = array(
array(''a'', ''b'', ''d''),
array(''e'', ''f'', ''h'')
);
// Configure the mock.
$mock->expects($this->any())
->method(''doSomething'')
->will($this->returnValueMap($map));
// $mock->doSomething() returns different values depending on
// the provided arguments.
$this->assertEquals(''d'', $stub->doSomething(''a'', ''b''));
$this->assertEquals(''h'', $stub->doSomething(''e'', ''f''));
}
}
Esta prueba pasa. Como puedes ver:
- cuando se llama a la función con los parámetros "a" y "b", se devuelve "d"
- cuando se llama a la función con los parámetros "e" y "f", se devuelve "h"
Por lo que puedo decir, esta característica se introdujo en PHPUnit 3.6 , por lo que es lo suficientemente "antigua" como para que se pueda usar con seguridad en prácticamente cualquier entorno de desarrollo o en etapas y con cualquier herramienta de integración continua.
La biblioteca PHPUnit Mocking (de forma predeterminada) determina si una expectativa coincide solo con el contador pasado al parámetro de expects
y la restricción transferida al method
. Debido a esto, dos llamadas esperadas que solo difieren en los argumentos pasados with
fallarán porque ambas coincidirán pero solo una verificará que tengan el comportamiento esperado. Vea el caso de reproducción después del ejemplo real de trabajo.
Para su problema, debe usar ->at()
o ->will($this->returnCallback(
como se describe en another question on the subject
.
Ejemplo:
<?php
class DB {
public function Query($sSql) {
return "";
}
}
class fooTest extends PHPUnit_Framework_TestCase {
public function testMock() {
$mock = $this->getMock(''DB'', array(''Query''));
$mock
->expects($this->exactly(2))
->method(''Query'')
->with($this->logicalOr(
$this->equalTo(''select * from roles''),
$this->equalTo(''select * from users'')
))
->will($this->returnCallback(array($this, ''myCallback'')));
var_dump($mock->Query("select * from users"));
var_dump($mock->Query("select * from roles"));
}
public function myCallback($foo) {
return "Called back: $foo";
}
}
Reproduce:
phpunit foo.php
PHPUnit 3.5.13 by Sebastian Bergmann.
string(32) "Called back: select * from users"
string(32) "Called back: select * from roles"
.
Time: 0 seconds, Memory: 4.25Mb
OK (1 test, 1 assertion)
Reproduzca por qué dos -> con () llamadas no funcionan:
<?php
class DB {
public function Query($sSql) {
return "";
}
}
class fooTest extends PHPUnit_Framework_TestCase {
public function testMock() {
$mock = $this->getMock(''DB'', array(''Query''));
$mock
->expects($this->once())
->method(''Query'')
->with($this->equalTo(''select * from users''))
->will($this->returnValue(array(''fred'', ''wilma'', ''barney'')));
$mock
->expects($this->once())
->method(''Query'')
->with($this->equalTo(''select * from roles''))
->will($this->returnValue(array(''admin'', ''user'')));
var_dump($mock->Query("select * from users"));
var_dump($mock->Query("select * from roles"));
}
}
Resultados en
phpunit foo.php
PHPUnit 3.5.13 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 4.25Mb
There was 1 failure:
1) fooTest::testMock
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-select * from roles
+select * from users
/home/.../foo.php:27
FAILURES!
Tests: 1, Assertions: 0, Failures: 1