使用phpunit

在快速开发,频繁上线的项目中,代码很容易出现问题,这时候做好单元测试就非常的重要。

1. 安装

项目地址:https://github.com/sebastianbergmann/phpunit/

手动安装很麻烦,推荐使用pear直接安装

yum install php-pear.noarch

pear install phpunit/PHPUnit
pear install phpunit/PHP_Invoker
pear install phpunit/PHPUnit_Selenium
pear install phpunit/PHPUnit_Story

2. 使用

'Car',
	);

	/**
	 * 不是test开头的测试方法使用@test
	 * 
	 * @test 注解么?
	 */
	public function noTest()
	{
		$this->assertEquals(1, 1);
		$this->assertEquals(1, 1);
		return false;
	}

	/**
	 * 依赖于noTest noTest返回值是这个参数值,noTest不过,这个检查跳过
	 * 
	 * @depends noTest
	 */
	public function testCreate($return)
	{
		$this->assertFalse($return, 'failed');
	}
	
	/**
	 * 使用数据源验验证测试
	 * 
	 * @dataProvider testProvider
	 */
	public function testArry($arr)
	{
		$this->assertArrayHasKey("return", $arr);
	}
	
	public function testProvider()
	{
		return array(
			array(array('return' => 'ss')),
			array(array('return' => 'ss')),
			array(array('return' => 'ss')),
		);
	}
	
	
	public function testException()
	{
		// 期待的异常
		$this->setExpectedException('Exception');
		// 抛出异常,测试通过
		throw new Exception('test');
	}

	/**
	 * 错误异常
	 * 
	 * @expectedException PHPUnit_Framework_Error
	 */
	public function testError()
	{
		// $this->fail('test');
		// PHP错误会抛出框架内置的异常 PHPUnit_Framework_Error
		include 'nofileffa.php';
	}
	
	public function setUp()
	{
		fwrite(STDOUT, 'set up');
		$this->queue = array(1, 2, 3);
	}
	
	public function tearDown()
	{
		fwrite(STDOUT, 'tear down');
		$this->queue = null;
	}
	
	public function testFixture()
	{
		$this->assertEquals(array(1, 2, ), $this->queue);
	}
	
	public function onNotSuccessfulTest(Exception $e)
	{
		fwrite(STDOUT, 'not success');
		parent::onNotSuccessfulTest($e);
	}
}

3. 部分资料

断言:http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions

fixtures:http://www.phpunit.de/manual/current/en/fixtures.html

注解:http://www.phpunit.de/manual/current/en/appendixes.annotations.html

yii test:http://www.yiiframework.com/doc/guide/1.1/zh_cn/test.overview   PHPUnit 3.7的authload换成了匿名函数,需要改一下,不然yii加载会有问题.

 

yii绑定onEndRequest事件的bug

yii在CListIterator中使用引用,导致迭代在特殊情况下会出现BUG

public function actionIndex()
{
    $data = array('a', 'b', 'c');
    $a = new CList($data);
    foreach ($a as $k => $v) {
        if ($k == 0) {
            unset($a[0]);
        }
        echo $v;
    }
}

如果预先不知道这个情况,很容易出现问题,框架中也有这个问题,看一段代码:

public function setGlobalState($key,$value,$defaultValue=null)
{
    // ...

    if($this->_stateChanged!==$changed)
        $this->attachEventHandler('onEndRequest',array($this,'saveGlobalState'));
}

// ...

/**
* Saves the global state data into persistent storage.
* @see getStatePersister
* @throws CException if the state persister is not available
*/
public function saveGlobalState()
{
    if($this->_stateChanged)
    {
        $this->_stateChanged=false;
        $this->detachEventHandler('onEndRequest',array($this,'saveGlobalState'));
        $this->getStatePersister()->save($this->_globalState);
    }
}

public function raiseEvent($name,$event)
{
    $name=strtolower($name);
    if(isset($this->_e[$name]))
    {
        foreach($this->_e[$name] as $handler)
        {
            // ...
        }
    }
    // ...
}

当runtime目录被清空时,onEndRequest事件触发saveGlobalState动作,在操作的过程中却又解除了saveGlobalState动作的绑定。这会导致raiseEvent事件的时候,原来N个动作变成N-1个动作,但是计算器还是N个,就会下标越界。

向官方提交了这个BUG,想修正的话可以先注释掉这行:

    $this->detachEventHandler('onEndRequest',array($this,'saveGlobalState'));

zf增加.html的后缀,即可以实现静态化的插件

之前是.php后缀的页面,如何只需把后缀改成.html,像:http://www.gudalu.com/index.php,访问:http://www.gudalu.com/index.html 就会自成生成静态文件呢?

前段时间古大陆的cpu有点高,处理txt章节不给,决定搞成这种自动静态,zend framework很方便,三步搞定

1.创建一个ZendEx_Plugin_Html的插件,用来处理写html

_filePath = $filePath;
	}
	public function dispatchLoopShutdown()
    {
		$htmlContent = $this->_response->__toString();
		$pathinfo = pathinfo($this->_filePath);
		!is_dir($pathinfo['dirname']) && mkdir($pathinfo['dirname'], 0777, true);
		file_put_contents($filePath, $htmlContent);
	}
}

1. 在入口把所有.html的请求全部转发到HtmlController中~

	$router = $front->getRouter();
	$htmlRoute = new Zend_Controller_Router_Route_Regex(
		'[\d\w\/]+\.html',
		array(
			'module' => 'default',
			'controller' => 'html',
			'action'     => 'handle'
		)
	);

 

2.HtmlController注册ZendEx_Plugin_Html插件,这里可以对uri进行处理

_request->getRequestUri();
		$requestUri = parse_url($requestUri);
		$requestPath = $requestUri['path'];
		// --- 通过条件定位对应的参数来静态化数据
		$module = 'default';
		$controller = $action = '';
		$params = array();
		if (preg_match('/^\/index.html$/', $requestPath, $rs)) {
			$controller = 'index';
			$action = 'index';
		}
		// --- 文章页
		if (preg_match('/^\/file\/([\d]+)\/index\.html$/', $requestPath, $rs)) {
			$id = $rs[1];
			$controller = 'file';
			$action = 'view';
			$params = array('id' => $id);
		}
		if (empty($controller) || empty($action)) {
			//$this->_showError('无法解析地址');
			$this->_closeView();
			$this->_response->setHeader('Status', '404 Not Found', true);
			return;
		}
		
		$this->_forward($action, $controller, $module, $params);
		$this->_closeView();
		$filePath = str_replace('_app', '_www', APP_PATH) . $requestPath;
		// --- 给更低的运行级别,在render之后运行
		$helperHtml = new ZendEx_Plugin_Html();
		$helperHtml->setFilePath($filePath);
		$helperHtml->setRequest($this->_request)->setResponse($this->_response);
		$this->getFrontController()->registerPlugin($helperHtml);
	}
}

 

国内虚拟主机架设dabr

基于dabr212修改的,下载附件,然后覆盖 common 目录下的 twitter.php 文件,请求API的速度大概在1秒左右比较慢,最好是自己用。

下载文件

update 20091030:国内的虚拟主机可以架设一个,将文件覆盖即可,方便手机访问。主要是将dabr原来curl请求 twitter api的方式,换成一个socket方试请求,使用一个twitter上推荐的,号称不会被封的ip,并且使用了https,测试功能一切正常。