Создание фиктивного объекта со свойствами по умолчанию

Мне нужно создать фиктивный объект с набором свойств по умолчанию, чтобы его можно было использовать в кодовой базе при создании экземпляра.

$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 создает моки, записывая код в строку и выполняя его eval(). Я просто не знаю, как включить объявления свойств в этот процесс.   -  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 и проверить их взаимосвязь. Когда я использую $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