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

发布于 2024-12-24 03:16:43 字数 1543 浏览 2 评论 0原文

使用 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 文档有点简单。

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

Using PHPUnit 3.6 I'm trying to test the exec() method in the below controller class. This method does two things:

  1. Determines the name of the method to call based on the object's existing properties, and ...
  2. If the determined controller method is callable it is executed and if not the method throws an exception

The (simplified) source code looks like this:

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!
    }
  }
}

So my problem is ...

I can't figure out how to get coverage on this part of the code:

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

because I'm not sure how to (or that it's even possible to) test the invocation of a method whose name is not known in the context of the abstract class. Again: the method to be called is declared in child classes. Normally I would just mock an abstract method but I can't in this case because the method doesn't exist yet -- it will be specified by a child class.

What might be the answer ...

  • ??? It may be possible that this line doesn't even need to be covered because it essentially relies on PHP's ability to correctly invoke a callable class method. If I successfully test that exec() throws an exception when it's supposed to, I know that correct functioning of the line in question depends on PHP functioning correctly. Does this invalidate the need to test it in the first place ???
  • If there is some way to mock the abstract class and create a method with a known name to add to the mocked class this would solve my problem and is what I've been trying unsuccessfully to do so far.
  • I know I could create a child class with a known method name but I don't believe it's a good idea to create a concrete child class just to test an abstract parent.
  • It could be that I need to refactor. One thing I don't want to do is leave child classes to implement the exec() function on their own.

What I've tried ...

  • Use some of PHP's reflection capabilities to no avail -- this may perhaps be due to my own inexperience with reflection and not its inability to handle this case, though.
  • Going back and forth through the PHPUnit manual and API docs. Unfortunately, as awesome as PHPUnit is, I often find the API documentation a bit light.

I would really appreciate any guidance on how best to proceed here. Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

凉薄对峙 2024-12-31 03:16:43

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

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

I disagree with your stipulation that "it's [not] a good idea to create a concrete child class just to test an abstract parent." I do this quite often when testing abstract classes and usually name the concrete subclass after the test to make it clear.

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

The code to make this test happen is trivial and can be easily verified by inspection.

I'm curious, why use is_callable instead of method_exists to avoid creating the array? It's probably just personal preference, but I'm wondering if there are any semantic differences.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文