【问题标题】:PHPUnit: Mock methods of existing objectPHPUnit:现有对象的模拟方法
【发布时间】:2011-10-28 16:05:10
【问题描述】:

PHPUnit 的 getMock($classname, $mockmethods) 基于给定的类名创建一个新对象,并让我更改/测试我指定的方法的行为。

我渴望不同的东西;它正在改变现有对象的方法的行为 - 无需构造新对象。

这可能吗?如果是,怎么做?


在考虑这个问题时,我得出的结论是,可以通过序列化对象,更改序列化字符串以使对象成为扩展旧类的 新类 的实例加上模拟的方法。 我想要一些代码——或者可能已经有这样的代码了。


虽然可以再次创建要模拟的对象,但在我的测试中这样做太复杂了。因此,如果我真的不是真的必须这样做,我不想这样做。这是一个 TYPO3 TSFE 实例,在引导过程中设置一次已经够难了。

【问题讨论】:

    标签: php object mocking phpunit


    【解决方案1】:

    我知道这个答案比较晚,但我觉得对于这个问题的未来观众来说,现在有一个更简单的解决方案(它有一些缺点,但根据您的需要可以更容易实现)。 Mockery 支持使用他们所谓的 "proxied partial mock." 模拟预先存在的对象他们说这是用于具有最终方法的类,但它可以在这种情况下使用(尽管文档确实警告说它应该是“不得已”)。

    $existingObjectMock = \Mockery::mock($existingObject);
    $existingObjectMock->shouldReceive('someAction')->andReturn('foobar');
    

    它的作用是创建一个代理对象,该对象将所有方法调用和属性获取/设置交给现有对象,除非它们被模拟。

    但应该注意的是,代理存在明显的问题,即任何类型检查或类型提示都失败。但这通常可以避免,因为$existingObject 仍然可以传递。只有在需要模拟功能时才应使用$existingObjectMock。

    【讨论】:

      【解决方案2】:

      首先让我说:欢迎来到单元测试的阴暗面。

      含义:您通常不想这样做,但正如您所解释的那样,您似乎有一个有效的用例。

      运行套件

      您可以很轻松地做的事情,不是微不足道,而是比更改应用程序架构更容易,就是使用 runkit 动态更改类行为

      runkit_method_rename(
          get_class($object), 'methodToMock', 'methodToMock_old'
      );
      runkit_method_add(
          get_class($object), 'methodToMock', '$param1, $param2', 'return 7;'
      );
      

      runkit::method_add

      并在测试后使用 method_remove 并再次重命名。我不知道有任何框架/组件可以帮助您解决这个问题,但在UglyTestsBaseTest extends PHPUnit_Framework_TestCase 中您自己实现的并不多。

      嗯……

      如果您只能访问该对象的引用(如:$x = new Foo(); 中的 $x),我不知道有什么办法可以说:$x,您现在属于 SomethingElse 类型指向该对象的所有其他变量也应该改变。

      我假设您已经知道 testing your privates 之类的东西,但这对您没有帮助,因为您无法控制 the objects life cycle。

      php 测试助手扩展

      注意:the Test-Helper extension is supersededhttps://github.com/krakjoe/uopz

      这里还可以帮助您的是:Stubbing Hard-Coded Dependencies 使用 php-test-helpers extension,它允许您执行 拦截对象创建之类的操作。

      这意味着,当您的应用程序调用 $x = new Something(); 时,您可以破解 PHP 以使 $x 然后包含 YourSpecialCraftedSomething 的实例。

      您可以使用 PHPUnit Mocking API 创建该类或自己编写。


      据我所知,这些是您的选择。如果值得去那里(或者只是为该项目编写集成/硒测试)是您必须自己弄清楚的事情,因为这在很大程度上取决于您的情况。

      【讨论】:

      • 由于没有其他答案,也没有更好的解决方案,我接受。
      【解决方案3】:

      并非所有预先存在的代码都可以测试。代码确实需要设计成可测试的。因此,虽然不完全符合您的要求,但您可以重构代码,使对象的实例化在一个单独的方法中,然后模拟该方法以返回您想要的。

      class foo {
        function new_bar($arg) { return new bar($arg); }
        function blah() {
          ...
          $obj = $this->new_bar($arg);
          return $obj->twiddle();
        }
      }
      

      然后你可以用它来测试它

      class foo_Test extends PHPUnit_Framework_TestCase {
        function test_blah() {
          $sut = $this->getMock('foo', array('new_bar', 'twiddle'));
          $sut->expects($this->once())->method('new_bar')->will($this->returnSelf());
          $sut->expects($this->once())->method('twiddle')->will($this->returnValue('yes'));
          $this->assertEquals('yes', $sut->blah());
        }
      }
      

      【讨论】:

      • 为什么对这个答案投反对票?我发现建议进行重构而不是使用黑魔法来“让事情正常进行”是完全可以理解的
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-08-22
      • 2013-09-12
      • 2011-03-14
      • 2013-05-30
      • 2010-09-25
      • 1970-01-01
      • 2013-04-16
      相关资源
      最近更新 更多