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

时间:2012-01-04 04:53:30

标签: php unit-testing phpunit

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

  1. 根据对象的现有属性确定要调用的方法的名称,并且......
  2. 如果确定的控制器方法是可调用的,则执行它,如果不是,则该方法抛出异常
  3. (简化)源代码如下所示:

    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文档有点亮。

    我真的很感激有关如何最好地继续这里的任何指导。提前谢谢。

1 个答案:

答案 0 :(得分:5)

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

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