3

我一直在寻找在 Yii 应用程序开发中使用 TDD 的最佳方式。如今,大多数 Web 应用程序都由一个前端 API 层(通常是 JSON)组成,以提供对服务器和后端的异步调用。就我而言,这个应用程序中最常用的测试是单元测试和功能测试。后者在指南和书籍中最广泛地展示了利用 PHPUnit + Selenium,但 Behat + Mink 似乎也很酷(但我对它还不是很自信)

如果您曾经使用过使用浏览器(如 Selenium)的功能测试,您就会知道运行它们的次数越少,您的感觉就越好。这导致它们更慢,更难维护,有时(如使用 JS SDK 的弹出式 FB 登录)很痛苦。

在使用单个网页应用程序时,我关心测试我的 api 的 JSON 输出。我想使用类似单元测试的方法来测试这些功能,以便更快地进行更易于维护的测试。考虑到我的控制器的大部分操作对使用 accessControl 过滤器的仅登录用户可用,我想知道让我的测试启动和运行的最佳方法。

此刻我认为有两种方法可以做到这一点

  • 使用 cUrl 指向所需的 enpoint 以获取 JSON 直接调用
  • 控制器的功能

在第一个场景中,我可以使用固定装置,但我无法使用 Apache 模拟 CWebUser 类(模拟登录用户),当 cUrl 出现时,它由我的 CWebApplication 实例执行,该实例不是由 PHPUnit 执行的。我可以通过使我的所有 API 调用无状态并因此删除 accessControl 过滤器来解决这个问题。

在第二个中,我发现模拟 CWebUser 类的唯一方法是在我正在执行的测试类中覆盖它。在我不需要测试需要不同类型用户的用例之前,这种方法是有效的,而且我无法在运行时(或设置时)更改我的网络用户模拟。我发现模拟我的网络用户的唯一方法是您可以在下面找到的方法,这导致 $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 输出没用?

此致

4

2 回答 2

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);
}
于 2013-01-25T18:39:58.080 回答
0

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

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

在您的测试类的 setUp 方法中,您可以登录任何用户(在这种情况下,利用我的固定装置)

//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,
), 

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

于 2013-02-07T22:44:05.603 回答