【问题标题】:Creating a mock object with default properties创建具有默认属性的模拟对象
【发布时间】:2012-02-17 15:23:39
【问题描述】:

我需要创建一个具有默认属性集的模拟对象,以便在实例化时可以在代码库中的其他软件中使用它。

$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';
}

编辑:简化示例

【问题讨论】:

  • 为了清楚起见,您想模拟一个现有的类,并为您的模拟提供该类的默认属性,而不实际实例化被模拟的类?
  • @Leigh 是的,该类将在框架中更深地实例化。我正在模拟的类是一个抽象模型,我想给它一些$fields,这样它就会像一个真实的模型一样。我知道 PHPUnit 通过将代码写入字符串并 evaling() 来创建模拟。我只是不知道如何在该过程中包含属性声明。

标签: php phpunit


【解决方案1】:

您对使用反射有何感想?

$r = new ReflectionClass('MyClass');

$props = $r->getDefaultProperties();

$mock = new stdClass;

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

我自己并没有大量使用反射,仅用于基本的内省。我不确定您是否能够使用它完全模仿可见性等,但我不明白为什么不继续写入字符串和evaling。

编辑:

出于好奇通过反射函数进行扫描,完全有可能使用虚拟方法完全模仿类,如果您在字符串中动态构建类和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);

【讨论】:

  • 如果可以的话,我会支持你两次。感谢您的努力。到目前为止,我还没有尝试过反射路线,但听起来很有趣。我会试试你的一些建议,看看效果如何:)
【解决方案2】:

我不确定您是否甚至需要出于测试目的这样做。

通常在测试涉及模型访问的代码时,您使用固定装置而不是模拟实际模型,因为模型是“哑”数据结构,不会公开任何需要模拟的功能。

您的示例证明了这一点:如果您不需要模拟行为(方法),则不需要模拟对象。您需要一个数据夹具,而不是模型用作其数据源。如果如您所说,“依赖注入不是一种选择”,则尤其如此。

当然,如果您决定还是要模拟模型,我建议您使用 @Leigh 的反射解决方案。

我昨天刚刚回答了一个关于数据库测试的问题,您可以查看更多详细信息: PHPUnit: How to test database interactions on remote Postgres server?

【讨论】:

  • 好点。我想你可以说我正在尝试创建一个动态装置。我正在尝试通过创建人造模型来测试底层模型框架。我们的模型几乎完全通过受保护的属性进行配置,因此我可以简单地通过 protected $_fields = array('firstname, 'lastname'); 创建用户模型。我的目标是能够通过动态构建一个假模型来重新创建任何情况来测试底层框架。我已经模拟了数据网关和框架的其余部分。我只需要能够使用默认道具创建动态类
  • 啊哈,我明白了。在这种情况下,一定要模拟这些模型。一般来说,我会将模型基于一个“集合”对象,该对象将所有属性存储在一个受保护的数组中,并使用魔术__get__set 实现ArrayAccess 以使属性读/写变得简单。如果你这样做了,你可以使用自己的 Model::load 方法从键/值数组中一次性填充模型。像这样的load 方法可以使使用您想要的任何信息填充对象变得非常简单。此外,如果需要,它可以通过具有验证等的子类进行扩展。
【解决方案3】:

我认为问题在于您必须让被测系统(您的框架)能够使用new 直接实例化模型对象,并且每个测试都需要为其属性设置不同的默认值。

如果是这种情况,您可以创建一个简单的基类,以便在构造时填充一组预定义的属性。下面的解决方案使用了 PHP 5.3 中的 late static binding,但您无需稍加调整即可轻松获得相同的结果。

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 一起使用的类名,这应该可以工作。如果您需要能够在对框架的一次调用期间创建多个相同模拟类的实例,则需要使用某种索引机制来增强上述内容。

【讨论】:

  • 用于模型测试。我想创建一个虚假的 Author and Books 模型并测试它们的关系。当我使用$author->books 时,框架需要实例化一个新的Book 对象并读取它的属性以用数据播种它并确定与Author 的关系标准。所以不,我不能真正在任何地方注入我的模拟 Book 对象,因为动态实例化这些对象是框架的工作。我只需要将具有这些属性的 Author 类放入内存中,而无需在代码中一致地定义该类。
  • 看看我在问题中的最后一个例子。我只需要能够将这些类写入内存而无需在任何地方进行硬编码。我以为我可以利用 PHPUnit 的 Mock Object Builder,但我不确定它是否具有执行此操作的功能。
  • @MikeB - 查看我的更新。这是您想要实现的目标吗?
猜你喜欢
  • 1970-01-01
  • 2019-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-09
  • 1970-01-01
  • 2022-01-07
相关资源
最近更新 更多