2013-01-22 40 views
3

我一直在寻找在Yii应用开发中使用TDD的最佳方式。现在大多数Web应用都由一个前端API层(通常为JSON)组成,以向服务器和后端提供异步调用。就我而言,在这个应用程序中最常用的测试是单元测试和功能测试。后者在指南和书籍中使用最广泛的PHPUnit + Selenium,但Behat + Mink看起来也很酷(但我还没有真正有信心)在Yii应用中使用Restful Api的TDD最佳实践

如果您曾经使用过使用浏览器的功能测试像Selenium),你知道越少你必须运行他们越好,你的感觉。这导致它们更慢,更难维护,并且有时(比如使用JS SDK的弹出式FB登录)是痛苦的。

当使用单个网页应用程序时,我关心测试我的apis的JSON输出。我想用类似单元测试的方法来测试这些功能,以便更快地测试,以便于维护。考虑到我的控制器的大部分操作都可用于使用accessControl过滤器的仅记录用户,我想知道如何让我的测试正常运行。

在这个时刻,我认为有两个方法来完成对所需enpoint得到JSON这

  • 使用卷曲直接调用
  • 控制器的功能

在第一个场景我可以使用fixtures,但是我没有办法模拟CWebUser类(模拟一个已记录的用户),当cUrl出现时使用Apache,它被我的CWebApplication的一个实例执行,它不是由PHPUnit执行的实例。我可以通过将所有API调用无状态来消除此问题,并因此删除accessControl过滤器。

在第二个中,我发现模拟CWebUser类的唯一方法是在我正在执行的测试类中覆盖它。这种方法支付,直到我不需要测试需要不同类型用户的用例,并且我无法在运行时(或在安装时)更改我的webuser模拟。我发现模拟我的web用户的唯一方法是你可以在下面找到的,这导致$ this-> getMock('WebUser')不会影响配置文件中定义的CWebApplication的WebUser(只读)单例。

又来了一个具体的例子:

class UserControllerTest extends CDbTestCase 
{ 
    public $fixtures=array(
      /* NEEDED FIXTURES*/ 
    ); 

    public function testUserCanGetFavouriteStore() { 

      $controller = new UserController(1); 
      $result = json_decode($controller->actionAjaxGetFavStore());      
      $this->assertInternalType('array', $result->data);    

      $model = $result->data[0]; 
      $this->assertEquals($model->name, "Nome dello Store"); 

    } 
} 

class WebUser extends CWebUser { 

    public function getId() { 
     return 1; 
    } 
    public function getIsGuest() { 
      return false; 
    } 
}; 

我在想,如果能或者通过API密钥或用户/密码组合与API接口进行身份验证,可能是有用的。 如果我转向几乎无状态的API集成,这很好,但是大多数时候我只有控制器的操作(只允许登录用户)返回Json数据来填充前端。

任何人都可以建议我一个更好的方法?也许这只是无用的测试这种JSON输出?

问候

回答

0

也许我过于简单化你的问题。听起来你想在运行测试之前模拟用户登录?如果是这样的话,为什么不在你的灯具中创建一个用户对象,并在运行测试之前实际登录它们,然后登出?

喜欢的东西:

/** 
* Sets up before each test method runs. 
* This mainly sets the base URL for the test application. 
*/ 
protected function setUp() 
{ 
    parent::setUp(); 

    // login as registered user 
    $loginForm = new UserLoginForm(); 
    $loginForm->email = USER_EMAIL; // use your fixture 
    $loginForm->password = USER_PASSWORD; // use your fixture 
    if(!$loginForm->login()) { 
     throw new Exception("Could not login in setup"); 
    } 
} 

protected function tearDown() 
{ 
    parent::tearDown(); 
    Yii::app()->user->logout(true); 
} 
+0

嗨Aj, 感谢您的回复,这不能工作,因为只要我使用标准的Cwebuser-> login()方法应用程序尝试在会话吐出数据写入标准输出内容,这使得phpunit引发错误。 –

0

好实际上是我和我的团队发现是创建一个存根WEBUSER类的唯一解决方案。 以这种方式重写WebUser类,您可以在不依赖会话的情况下对用户进行身份验证。

class WebUserMock extends WebUser { 

public function login($identity,$duration=0) 
{ 
    $id=$identity->getId(); 
    $states=$identity->getPersistentStates(); 
    if($this->beforeLogin($id,$states,false)) 
    { 
     $this->changeIdentity($id,$identity->getName(),$states); 
     $duration = 0; 
     if($duration>0) 
     { 
      if($this->allowAutoLogin) 
       $this->saveToCookie($duration); 
      else 
       throw new CException(Yii::t('yii','{class}.allowAutoLogin must be set true in order to use cookie-based authentication.', 
        array('{class}'=>get_class($this)))); 
     } 

     $this->afterLogin(false); 
    } 
    return !$this->getIsGuest(); 
} 

public function changeIdentity($id,$name,$states) 
{ 
    $this->setId($id); 
    $this->setName($name); 
    $this->loadIdentityStates($states); 
} 

// Load user model. 
protected function loadUser() { 
    $id = Yii::app()->user->id; 
     if ($id!==null) 
      $this->_model=User::model()->findByPk($id); 
    return $this->_model; 
} 
}; 

在您的测试类的设置方法,您可以登录任何用户(在这种情况下,利用我的灯具)

//a piece of your setUp() method.... 
$identity = new UserIdentity($this->users('User_2')->email, md5('demo'));    
$identity->authenticate();  
if($identity->errorCode===UserIdentity::ERROR_NONE)          
    Yii::app()->user->login($identity);    

至于做只是覆盖在测试配置的用户部件的最后一件事文件,并告诉他用这一个:

保护/配置/ test.php的

'user'=>array(
    'class' => 'application.tests.mock.WebUserMock', 
    'allowAutoLogin'=>false, 
), 

仍然不确定这是最好的方式来处理它,但似乎工作正常