4

我一直在使用 Zend Framework 进行单元测试。我已经习惯了它提供的其他东西,但我很难理解 Mock Objects。

对于这个例子,我试图使用一个模拟对象来测试我的模型。

<?php

class Twitter_Model_Twitter
{
    private $_twitter;

    /**
     * Make the options injectable.
     * __contruct($auth, $key)
     */
    public function __construct() 
    {
        $config      = new Zend_Config_Ini(APPLICATION_INI, APPLICATION_ENV);
        $key         = $config->encryption->salt;
        $iv_size     = mcrypt_get_iv_size(MCRYPT_XTEA, MCRYPT_MODE_ECB);
        $iv          = mcrypt_create_iv($iv_size, MCRYPT_RAND);
        $password    = mcrypt_decrypt(MCRYPT_XTEA, $key, $password, MCRYPT_MODE_ECB, $iv);

        $this->_twitter = new Zend_Service_Twitter($username, $password);
    }


    public function verifyCredentials()
    {
        return $this->_twitter->account->verifyCredentials();
    }

    public function friendsTimeline($params)
    {
        return $this->_twitter->status->friendsTimeline($params);
    }
}

对于我的单元测试:

require_once ('../application/models/Twitter.php');
class Model_TwitterTest extends ControllerTestCase
{
    /**
     * @var Model_Twitter
     */
    protected $_twitter;

    public function testfriendsTimeline() 
    {
        $mockPosts = array('foo', 'bar');    

        //my understanding below is:
        //get a mock of Zend_Service_Twitter with the friendsTimeline method
        $twitterMock = $this->getMock('Zend_Service_Twitter', array('friendsTimeline'));
        /*
        line above will spit out an error:
        1) testfriendsTimeline(Model_TwitterTest)
        Missing argument 1 for Mock_Zend_Service_Twitter_9fe2aeaa::__construct(), called in 
        /Applications/MAMP/bin/php5/lib/php/PHPUnit/Framework/TestCase.php on line 672 and
        defined /htdocs/twitter/tests/application/models/TwitterTest.php:38
        */

        $twitterMock->expects($this->once())
              ->method('friendsTimeline')
              ->will($this->returnValue($mockPosts));

        $model = new Twitter_Model_Twitter();
        $model->setOption('twitter', $twitterMock);

        $posts = $model->friendsTimeline(array('count'=>20));
        $this->assertEquals($posts, $mockPosts);
    }
}

您将如何测试以下内容?1) verifyCredentials() 2) FriendsTimeline()

谢谢,温伯特

4

1 回答 1

1

我要回答这个问题。我想我已经完成了这项工作,这要归功于#zftalk 的 zomg。

这是我的Twitter 模型:

<?php
//application/models/Twitter.php
class Twitter_Model_Twitter
{
    private $_twitter;
    private $_username;
    private $_password;

    public function __construct(array $options = null) 
    {        
        if (is_array($options)) {
            $this->setOptions($options);
            $this->_twitter = new Zend_Service_Twitter($this->_username, $this->_password);
        } else {
            $twitterAuth = new Zend_Session_Namespace('Twitter_Auth');
            $config      = new Zend_Config_Ini(APPLICATION_INI, APPLICATION_ENV);
            $key         = $config->encryption->salt;
            $iv_size     = mcrypt_get_iv_size(MCRYPT_XTEA, MCRYPT_MODE_ECB);
            $iv          = mcrypt_create_iv($iv_size, MCRYPT_RAND);
            $password    = mcrypt_decrypt(MCRYPT_XTEA, $key, $twitterAuth->password, MCRYPT_MODE_ECB, $iv);

            $username    = $twitterAuth->username;
            $this->_twitter = new Zend_Service_Twitter($username, $password);
        }
    }

    public function setOptions(array $options)
    {
        $methods = get_class_methods($this);
        foreach ($options as $key => $value) {
            $pieces = explode('_', $key);
            foreach($pieces AS $piece_key => $piece_value) {
                $pieces[$piece_key] = ucfirst($piece_value);
            }

            $name = implode('',$pieces);
            $method = 'set' . $name;
            //$method = 'set' . ucfirst($key);
            if (in_array($method, $methods)) {
                $this->$method($value);
            }
        }
        return $this;
    }

    //I added this method. So that I could "inject"/set the $_twitter obj
    public function setTwitter($obj)
    {
        $this->_twitter = $obj;
        return $this;
    }


    public function verifyCredentials()
    {
        return $this->_twitter->account->verifyCredentials();
    }

    public function friendsTimeline($params)
    {
        return $this->_twitter->status->friendsTimeline($params);
    }
    //in the real code, more will go here...
}

在我的单元测试中,我有这个:

<?php
// tests/application/models/TwitterTest.php
require_once ('../application/models/Twitter.php');

class Model_TwitterTest extends ControllerTestCase
{
    public function testVerifyCredentials() 
    {
        $stub = $this->getMock('Zend_Service_Twitter', array('verifyCredentials'),array(),'',FALSE);
        //FALSE is actually the 5th parameter to flag getMock not to call the main class. See Docs for this.
        //Now that I have set the $_twitter variable to use the mock, it will not call the main class - Zend_Rest_Client (i think)

        $stub->expects($this->once())
              ->method('verifyCredentials');

        $model = new Twitter_Model_Twitter();

        //this is the part where i set the $_twitter variable in my model to use the $stub
        $model->setOptions(array('twitter'=>$stub)); 

        $model->verifyCredentials();
    }
}

无论如何,我想我得到了它的工作。

1) 单元测试不再尝试连接 twitter.com:80

2) 在我让 setOptions() 在 Twitter_Model 中工作后,我的单元测试中的 $model->verifyCredentials() 被成功调用。

我将等待 Stackoverflow 中的其他人确认这是正确的答案。在此期间,想听听你们的意见。

谢谢!!!

于 2009-08-10T09:22:12.980 回答