使用PHPUnit模拟未在测试类中指定的以编程方式确定的方法

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

>根据对象的现有属性确定要调用的方法的名称,并…
>如果确定的控制器方法是可调用的,则执行它,如果不是,则该方法抛出异常

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

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文档有点轻松.

我真的很感激有关如何最好地在这里进行的任何指导.提前致谢.

最佳答案
我不同意你的规定,“创建一个具体的子类只是为了测试一个抽象的父母,这不是一个好主意.”我在测试抽象类时经常这样做,并且通常在测试之后命名具体的子类以使其清楚.

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来避免创建阵列?这可能只是个人偏好,但我想知道是否存在任何语义差异.

转载注明原文:使用PHPUnit模拟未在测试类中指定的以编程方式确定的方法 - 代码日志