【问题标题】:Using PHPUnit to mock a programatically determined method not specified in the class under test使用 PHPUnit 模拟未在被测类中指定的以编程方式确定的方法
【发布时间】:2012-01-04 04:53:29
【问题描述】:

使用 PHPUnit 3.6 我正在尝试在下面的控制器类中测试 exec() 方法。这个方法做了两件事:

  1. 根据对象的现有属性确定要调用的方法的名称,并且...
  2. 如果确定的控制器方法可调用,则执行该方法,否则该方法将引发异常

(简化的)源代码如下所示:

abstract class CLIController extends Controller
{
  /* irrelevant class details here */

  public function exec()
  {
    $action = ! empty($this->opts->args[0])
      ? $this->opts->args[0]
      : $this->default_action;

    if ( ! $action || ! is_callable(array($this, $action))) {
      $msg = 'Invalid controller action specified';
      throw new LogicException($msg);
    } else {
      $this->$action(); // <---- trying to get code coverage on this line!
    }
  }
}

所以我的问题是...

我不知道如何覆盖这部分代码:

} else {
  $this->$action();
}

因为我不确定如何(或者甚至可能)测试在抽象类的上下文中名称未知的方法的调用。 再次声明:要调用的方法在子类中声明。 通常我会模拟一个抽象方法,但在这种情况下我不能,因为该方法还不存在——它将被指定由一个子类。

答案可能是什么...

  • ???可能甚至不需要覆盖这一行,因为它本质上依赖于 PHP 正确调用可调用类方法的能力。如果我成功测试exec() 在应该抛出异常时抛出异常,我知道相关行的正确运行取决于 PHP 是否正常运行。这是否会使首先对其进行测试的需要无效???
  • 如果有某种方法可以模拟抽象类并创建一个具有已知名称的方法以添加到模拟类,这将解决我的问题,这也是我一直在尝试但没有成功的方法到目前为止。
  • 我知道我可以创建一个具有已知方法名称的子类,但我认为创建一个具体的子类只是为了测试一个抽象父类并不是一个好主意。
  • 可能是我需要重构。我不想做的一件事是让子类自己实现exec() 函数。

我尝试过的...

  • 使用 PHP 的某些反射功能无济于事 - 这可能是由于我自己对反射缺乏经验,而不是它无法处理这种情况。
  • 反复阅读 PHPUnit 手册和 API 文档。不幸的是,尽管 PHPUnit 很棒,但我经常发现 API 文档有点简单。

对于如何在此处进行最佳操作的任何指导,我将不胜感激。提前致谢。

【问题讨论】:

    标签: php unit-testing phpunit


    【解决方案1】:

    我不同意您的规定,即“创建一个具体的子类只是为了测试一个抽象的父类[不是] 一个好主意。”我在测试抽象类时经常这样做,并且通常在测试后命名具体的子类以使其清楚。

    class CLIControllerTest extends PHPUnit_Framework_TestCase
    {
        public function testCallsActionMethod()
        {
            $controller = new CLIControllerTest_WithActionMethod(...);
            // set $controller->opts->args[0] to 'action'
            $controller->exec();
            self::assertTrue($controller->called, 'Action method was called');
        }
    }
    
    class CLIControllerTest_WithActionMethod extends CLIController
    {
        public $called = false;
    
        public function action() {
            $this->called = true;
        }
    }
    

    进行此测试的代码很简单,可以通过检查轻松验证。

    我很好奇,为什么使用is_callable 而不是method_exists 来避免创建数组?这可能只是个人喜好,但我想知道是否有任何语义差异。

    【讨论】:

    • 应该也可以模拟类并在 $methods array(); 中指定操作方法;但在这种情况下,我希望它在测试中尽可能具有可读性,所以我绝对同意你的方法。
    • @edorian - 模拟不起作用,因为它使用了__call,因为父类没有声明该方法,is_callablemethod_exists 没有检测到这一点。跨度>
    • @David 看,这就是为什么像我这样的人需要 SO。你盯着一个问题看够久了,你就失去了远见!我从来没有想过在实际的测试文件中创建一个子类。感谢您的意见。
    • @DavidHarkness 我使用is_callable 来确保可以从当前范围调用一个方法,而不仅仅是它存在——这个链接总结了它:method_exists() vs. is_callable()
    • @rdlowrey - 太好了,谢谢。此外,您可能希望在异常消息中包含 $action 以简化调试,例如"Action '$action' 必须是公共方法"。
    猜你喜欢
    • 2011-04-25
    • 1970-01-01
    • 2016-04-21
    • 2019-03-13
    • 1970-01-01
    • 2013-04-05
    • 1970-01-01
    • 1970-01-01
    • 2018-05-26
    相关资源
    最近更新 更多