unit test一个创建对象的方法

时间:2009-11-13 13:01:37

标签: php unit-testing oop tdd phpunit

我正试着绕过单元测试,还有一件我需要找到的拼图。

我要做的是为以下代码编写测试。在这种情况下,我有一个非常简单的前端控制器(用PHP编写)。

class frontController
{
   public function routeRequest($oRequest)
   {
      $sClassname = $oRequest->getController();
      $sMethod = $oRequest->getAction();

      $oController = new $sClassname();

      $oResponse = $oController->{$sMethod}($oRequest);

      return $oResponse;
   }

}

我遇到的问题是因为代码创建了新对象。我可以轻松模拟请求对象,以便我可以严格控制它在我的测试用例中实际执行的操作。我不确定用测试双重替换控制器的最佳方法。

This article from IBM建议使用工厂方法创建我的控制器,然后用一个用于测试的特定类覆盖它:

class frontController
{
   public function routeRequest($oRequest)
   {
      $sMethod = $oRequest->getAction();

      $oController = $this->createController($oRequest);
      $oResponse = $oController->{$sMethod}($oRequest);

      return $oResponse;
   }

   protected function createController($oRequest)
   {
      $sClassname = $oRequest->getController();
      return new $sClassname();
   }

}

然后测试可能是这样的:

class testFrontController extends frontController
{
   public function setMockController($oMockController)
   {
      $this->oMc = $oMockController;
   }

   protected function createController($oRequest)
   {
      return $this->oMockController;
   }
}

(注意这不是文章所说的,但我认为如果它这样做对我来说最有用)

另一种解决方案可能是创建另一个创建控制器的类。这将是frontController的依赖类。这样我就可以在测试过程中用test double替换工厂/创建类。像这样:

class frontController
{
   public function routeRequest($oRequest, $oControllerFactory)
   {
      $sMethod = $oRequest->getAction();

      $oController = $oControllerFactory->create($oRequest);
      $oResponse = $oController->{$sMethod}($oRequest);

      return $oResponse;
   }
}

class controllerFactory
{
   public function create($oRequest)
   {
      $sClassname = $oRequest->getController();
      return new $sClassname();
   }
}

我想依赖注入可以在前端控制器构造函数中处理,也可以通过setter而不是实际“route”方法的参数来处理。

我认为我更喜欢选项2。

这两种方法中的任何一种都是测试此类事物的正确方法吗?

(也许“好方法”会更好用!)

关于选项1与选项2的任何想法或建议表示赞赏,或者确实存在任何替代方案。记住 - 关键是如何测试一个自身创建其他对象的对象作为其执行的一部分。

谢谢!

4 个答案:

答案 0 :(得分:4)

您可能会发现this article方便。

讨论如何将对象创建与应用程序的实际运行分开。

答案 1 :(得分:2)

我通常认为工厂在这种情况下是一件好事。除了可交换性方面,它还意味着工厂可以存储正在创建的对象所需的其他参数,数据或依赖项,因此实际请求新对象的对象不必知道任何有关它们的信息。 ..

答案 2 :(得分:0)

你不想使用真正的控制器而是模拟,对吗?

在我看来,实现这一目标的最简单方法是将请求子类化,以便返回MockController的名称。

答案 3 :(得分:0)

我假设您已经仔细考虑过您的断言,以便确定您正在测试的目标。请记住,单元测试将测试您的方法的返回,在这种情况下,这是$ oResponse(无论这可能是什么)。因此,您的测试断言将基于此返回值。由于我不知道您的代码片段的返回值是什么,因此我只能演示您可以完成的示例。

我建议PHPUnit进行测试,因为它似乎是PHP imho最完整的软件包(许多人都是SimpleTest的粉丝,以及他们自己的每个人)。

它看起来像这样(请注意我为了简洁而遗漏了包含。请阅读PHPUnit documentation以获取更多信息):

class AimTest extends PHPUnit_Framework_TestCase{
      private $_controller = null;
      private $_request = null;

      public function setUp(){
             $this->_controller = new frontController();
             //what does this object's type?
             $this->_request = new requestObject();  
      }

      public function testObjectCreation(){
            /*
             * note, that this is only one of several assertions that could
             * be made depending on the return value
             */
             $return = $this->_controller->routeRequest($this->_request);
             //tailor to what you expect your output to be
             $this->assertTrue($return == "my expected output");
      }

希望我完全没有错过你声明的目的。故事的道德是你只能测试你的方法返回的内容。如果要从方法测试对象实例化,请对实例化后返回该对象的方法使用instanceof PHP函数。