Създаване на макет обект със свойства по подразбиране

Трябва да създам макет обект с набор от свойства по подразбиране, така че да може да се използва друг софтуер в кодовата база при инстанциране.

$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' ?

Тествам част от рамката, която определя, локализира и инстанцира тези класове, така че инжектирането на подигравания обект би провалило целта на теста.

Не е нужно да се подигравам с никакви методи... просто създайте динамично подиграван клас така:

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

Редактиране: Опростен пример


person Mike B    schedule 17.02.2012    source източник
comment
Само за по-голяма яснота, искате да осмиете съществуващ клас и да дадете на своя макет свойствата по подразбиране на този клас, без всъщност да инстанциирате класа, който се подиграва?   -  person Leigh    schedule 17.02.2012
comment
@Leigh Правилно, класът ще бъде инстанциран по-дълбоко в рамката. Класът, на който се подигравам, е абстрактен модел и искам да му дам малко $fields, така че да се държи като истински модел. Знам, че PHPUnit създава подигравки, като записва кода в низ и го evaling(). Просто не знам как да включа имотните декларации в този процес.   -  person Mike B    schedule 17.02.2012


Отговори (3)


Как се чувствате относно използването на Reflection?

$r = new ReflectionClass('MyClass');

$props = $r->getDefaultProperties();

$mock = new stdClass;

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

Самият аз не съм използвал Reflection много, само за основна интроспекция. Не съм сигурен дали ще можете да имитирате напълно видимостта и т.н., като го използвате, но не виждам защо не, ако продължите по пътя на писане на низ и evaling.

Редактиране:

Сканирано чрез функциите Reflection от любопитство, е напълно възможно да се имитира напълно класът с фиктивни методи, прилагайки пълни ограничения на видимостта, константи и статични елементи, където е подходящо, ако динамично изградите класа в низ и го eval.

Въпреки това изглежда, че ще бъде пълна мисия наистина напълно да поддържате всяка възможност, когато става дума за правилни типове данни (ще ви е необходим код за повторно изграждане на конструктор на масиви от масив например)

Успех, ако тръгнете по този път, изисква повече умствена сила, отколкото съм готов да отделя в момента :)

Ето малко код, можете да направите същото с константи и да създадете празни методи по подобен начин.

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);
person Leigh    schedule 17.02.2012
comment
Бих гласувал за вас два пъти, ако можех. Благодаря, че положихте усилия.. Досега не съм пробвал маршрута за отражение, но звучи интересно. Ще пробвам с някои от вашите предложения и ще видя как ще стане :) - person Mike B; 17.02.2012

Не съм сигурен дори трябва да правите това за тестови цели.

Обикновено, когато тествате код, който включва достъп до модела, вие използвате приспособления, вместо да се подигравате на действителните модели, тъй като моделите са "тъпи" структури от данни, които не разкриват никакви възможности, които трябва да бъдат подигравани.

Вашият пример потвърждава това: ако нямате нужда да се подигравате на поведение (методи), нямате нужда от подиграващ обект. Вместо това имате нужда от фиксиране на данни, което моделът използва като свой източник на данни. Това е особено вярно, ако, както казвате, „инжектирането на зависимост не е опция“.

Разбира се, ако все пак решите, че искате да се подиграете на модела, бих предложил решението за отражение на @Leigh.

Вчера току-що отговорих на въпрос относно тестването на бази данни, който можете да разгледате за малко повече подробности: PHPUnit: Как да тествам взаимодействия с база данни на отдалечен сървър на Postgres?

person rdlowrey    schedule 17.02.2012
comment
Добри точки. Предполагам, че може да се каже, че се опитвам да създам динамично приспособление. Опитвам се да тествам основната рамка на модела, като създавам фалшиви модели. Нашите модели са конфигурирани почти изцяло чрез защитени свойства, така че мога да създам потребителски модел просто като имам protected $_fields = array('firstname, 'lastname');. Моята цел е да имам способността да пресъздам всяка ситуация чрез динамично изграждане на фалшив модел, за да тествам основната рамка. Вече се подиграх на шлюзовете за данни и останалата част от рамката. Просто имам нужда от способността да създавам динамични класове с подпори по подразбиране - person Mike B; 17.02.2012
comment
Аха разбирам. В такъв случай по всякакъв начин се подигравайте на тези модели. Като цяло с нещо подобно бих базирал модела на колекция обект, който съхранява всички свойства в един защитен масив и бих имплементирал ArrayAccess с магия __get и __set, за да направя четенето/записът на свойството лесно. Ако сте направили това, можете да добавите свой собствен Model::load метод, за да попълните модел с един ход от масив ключ/стойност. Метод load като този би направил попълването на обекта с всяка информация, която искате, доста лесно. Освен това може да се разшири от дъщерни класове с валидиране и т.н., ако е необходимо. - person rdlowrey; 17.02.2012

Мисля, че проблемът е, че трябва да имате тествана система (вашата рамка) да може да използва new за директно инстанциране на моделни обекти и всеки тест трябва да задава стойностите по подразбиране за своите свойства по различен начин.

Ако случаят е такъв, можете да създадете прост базов клас за попълване на предварително дефиниран набор от свойства при изграждането. Решението по-долу използва късно статично обвързване от PHP 5.3, но лесно бихте могли да постигнете същите резултати и без него с леко ощипване.

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);
}

Докато можете да предоставите името на класа, което да използвате с new към вашата рамка, това трябва да работи. Ако трябва да можете да създадете повече от един екземпляр на един и същи макет клас по време на едно извикване на вашата рамка, ще трябва да подобрите горното с някакъв механизъм за индексиране.

person David Harkness    schedule 17.02.2012
comment
Това е за тестване на модела. Искам да създам фалшив модел на автор и книги и да тествам връзката им. Когато използвам $author->books рамката трябва да създаде нов Book обект и да прочете неговите свойства, за да го зареди с данни и да определи критериите за връзка към/от Author. Така че не, наистина не мога да инжектирам моя подиграван Book обект никъде, тъй като работата на рамките е да създава тези обекти динамично. Просто трябва да хвърля Author клас в паметта с тези свойства, без съгласувано да дефинирам този клас в кода. - person Mike B; 17.02.2012
comment
Вижте последния ми пример във въпроса. Просто трябва да мога да записвам такива класове в паметта, без да го кодирам някъде. Мислех, че мога да използвам Mock Object Builder от PHPUnit, но не съм сигурен, че има функционалността да направи това. - person Mike B; 17.02.2012
comment
@MikeB - Вижте моята актуализация. Това ли се стремите да постигнете? - person David Harkness; 18.02.2012