2012-02-17 24 views
11

Necesito crear un objeto simulado con un conjunto predeterminado de propiedades para que pueda usarse elseware en la base de código al momento de la instanciación.Creación de un objeto simulado con propiedades predeterminadas

$mock = $this->getMock('MyClass', array(), array(), 'MyClass_Mock'); 
$mock->prop = 'foobar'; 

$myclassMock = new get_class($mock); 
var_dump($myclassMock->prop); // NULL 
// How can I make this dump 'foobar' ? 

Estoy probando parte del marco que determina, localiza y crea instancias de estas clases de manera de inyectar el objeto burlado sería contrario al propósito de la prueba.

No necesito a burlarse de los métodos .. acaba de crear dinámicamente una clase burlado de este modo:

class MyClass_Mock extends MyClass { 
    public $prop = 'foobar'; 
} 

Editar: ejemplo simplificado

+0

Para mayor claridad, ¿quieres burlar una clase existente y darle a tu simulacro las propiedades predeterminadas de esa clase, sin crear una instancia de la clase que se está burlando? – Leigh

+0

@Leigh Right, la clase se instanciará más profundamente en el marco. La clase de la que estoy burlando es un modelo abstracto, y quiero darle algunos '$ campos' para que se comporte como un modelo real. Sé que PHPUnit crea burlas al escribir el código en una cadena y evaluarlo(). Simplemente no sé cómo incluir declaraciones de propiedades en ese proceso. –

Respuesta

4

¿Cómo se siente acerca del uso de la reflexión?

$r = new ReflectionClass('MyClass'); 

$props = $r->getDefaultProperties(); 

$mock = new stdClass; 

foreach ($props as $prop => $value) { 
    $mock->$prop = $value; 
} 

No he usado Reflection mucho solo, solo para la introspección básica. No estoy seguro si podrá imitar completamente la visibilidad, etc. usarlo, pero no veo por qué no si continúa por la ruta de escribir en una cadena y eval ing.

Editar:

escaneada a través de las funciones de Reflexión por curiosidad, es totalmente posible imitar plenamente la clase con métodos ficticias, la implementación de restricciones visibilidad completa, constantes y elementos estáticos en su caso si dinámicamente construye la clase en una cadena y eval.

Sin embargo, parece que va a ser una completa misión para apoyar realmente plenamente todas las posibilidades cuando se llega a conseguir los tipos de datos correctos (necesitará código para reconstruir un constructor de matrices de una matriz, por ejemplo)

mejor de la suerte si vas por este camino, se requiere más energía del cerebro que yo estoy dispuesto a ahorrar ahora mismo :)

Aquí hay un poco de código, puede hacer lo mismo con las constantes, y crea métodos vacíos de una manera similar.

class Test 
{ 
    private static $privates = 'priv'; 
    protected $protected = 'prot'; 
    public $public = 'pub'; 
} 

$r = new ReflectionClass('Test'); 

$props = $r->getDefaultProperties(); 

$mock = 'class MockTest {'; 

foreach ($props as $prop => $value) { 
    $rProp = $r->getProperty($prop); 


    if ($rProp->isPrivate()) { 
     $mock .= 'private '; 
    } 
    elseif ($rProp->isProtected()) { 
     $mock .= 'protected '; 
    } 
    elseif ($rProp->isPublic()) { 
     $mock .= 'public '; 
    } 

    if ($rProp->isStatic()) { 
     $mock .= 'static '; 
    } 

    $mock .= "$$prop = "; 

    switch (gettype($value)) { 
     case "boolean": 
     case "integer": 
     case "double": 
      $mock .= $value; 
      break; 
     case "string": 
      $mock .= "'$value'"; 
      break; 
/* 
"array" 
"object" 
"resource" 
*/ 
    case "NULL": 
      $mock .= 'null'; 
      break; 
    } 

    $mock .= ';'; 
} 

$mock .= '}'; 

eval($mock); 

var_dump(new MockTest); 
+2

Te hubiera votado dos veces si pudiera. Gracias por hacer el esfuerzo. No he probado la ruta de reflexión hasta ahora, pero parece interesante. Voy a intentar algunas de tus sugerencias y ver cómo va :) –

3

no estoy seguro de que ni siquiera necesita para hacer esto para propósitos de prueba.

Por lo general, cuando se prueba código que involucra el acceso del modelo, se utilizan accesorios en lugar de burlarse de los modelos reales porque los modelos son estructuras de datos "tontas" que no exponen ninguna capacidad que deba ser burlada.

Su ejemplo lo confirma: si no necesita simular comportamiento (métodos), no necesita un objeto falso. En su lugar, necesita un accesorio de datos que el modelo utiliza como fuente de datos. Esto es especialmente cierto si, como dices, "inyección de dependencia no es una opción".

Por supuesto, si decides que quieres burlarte del modelo de todos modos, te sugiero la solución de reflexión de @ Leigh.

Me acaba de responder una pregunta sobre la prueba de la base de datos ayer que debes revisar para un poco más de detalle: PHPUnit: How to test database interactions on remote Postgres server?

+0

Buenos puntos. Creo que podrías decir que estoy tratando de crear un accesorio dinámico. Estoy intentando probar el marco del modelo subyacente creando modelos falsos. Nuestros modelos están configurados casi en su totalidad a través de propiedades protegidas, por lo que puedo crear un modelo de usuario simplemente con 'protected $ _fields = array ('firstname,' lastname ');'. Mi objetivo es tener la capacidad de recrear cualquier situación construyendo dinámicamente un modelo falso para probar el marco subyacente. Ya me he burlado de las puertas de enlace de datos y el resto del framework. Solo necesito la capacidad de crear clases dinámicas con los apoyos por defecto –

+0

Aha que veo. En ese caso, por supuesto, burlarse de esos modelos. En general, con algo así basaría el modelo en un objeto de "colección" que almacenase todas las propiedades en una única matriz protegida e implementara 'ArrayAccess' con magic' __get' y '__set' para hacer que la propiedad lectura/escritura sea simple. Si lo hiciera, podría lanzar su propio método 'Model :: load' para rellenar un modelo en un solo trazo a partir de una matriz clave/valor. Un método de "carga" como ese haría que poblar el objeto con cualquier información que quisiera sea bastante simple. Además, podría extenderse por clases de niños con validación, etc., si es necesario. – rdlowrey

1

Creo que la cuestión es que debe tener el sistema bajo prueba (el marco) sea capaz de usar new para instanciar objetos modelo directamente, y cada prueba necesita establecer los valores predeterminados para sus propiedades de manera diferente.

Si este es el caso, puede crear una clase base simple para rellenar un conjunto predefinido de propiedades al momento de la construcción. La siguiente solución usa late static binding de PHP 5.3, pero podría lograr fácilmente los mismos resultados sin un ligero ajuste.

class MockModel 
{ 
    public static $properties; 

    public function __construct() { 
     if (isset(static::$properties) && is_array(static::$properties)) { 
      foreach (static::$properties as $key => $value) { 
       $this->$key = $value; 
      } 
     } 
    } 
} 

class MockBook extends MockModel { /* nothing needed */ } 

function testBookWithTitle() { 
    MockBook::$properties = array(
     'title' => 'To Kill a Mockingbird' 
    ); 
    $book = new MockBook; 
    self::assertEquals('To Kill a Mockingbird', $book->title); 
} 

Mientras que puede proporcionar el nombre de clase para usar con new a su marco, esto debería funcionar. Si necesita poder crear más de una instancia de la misma clase simulada durante una única llamada a su marco, deberá mejorar lo anterior con algún tipo de mecanismo de indexación.

+0

Es para pruebas de modelo. Quiero crear un modelo falso de Autor y Libros y probar su relación. Cuando uso '$ author-> books' el framework necesita crear una instancia de un nuevo objeto' Book' y leer sus propiedades para sembrarlo con datos y determinar los criterios de relación a/from 'Author'. Así que no, realmente no puedo inyectar mi objeto 'Book' burlado en ninguna parte, ya que es el trabajo de frameworks crear instancias de estos objetos dinámicamente. Solo necesito lanzar una clase 'Author' en la memoria con esas propiedades sin definir de manera concertada esa clase en el código. –

+0

Mira mi último ejemplo en la pregunta. Solo necesito poder escribir esos tipos de clases en la memoria sin tener que codificarla en ninguna parte. Pensé que podría aprovechar el Mock Object Builder desde PHPUnit, pero no estoy seguro de que tenga la funcionalidad para hacerlo. –

+0

@MikeB - Ver mi actualización. ¿Es esto lo que estás buscando lograr? –

Cuestiones relacionadas