CakePHP的测试方法
注意
- PHPUnit 4 与 CakePHP 的单元测试不兼容
- 在 app/Config/core.php 文件中的调试(debug)级别至少是 1。当调试级别是 0 时,无法通过 web 运行器访问测试。在运行任何测试之前,应当确保 添加 $test 数据库配置。该配置被 CakePHP 用于测试夹具(fixture)的表和数据
- 当使用 PHPUnit 3.6+ 时,所有的输出都会被吞没。如果使用 CLI,可以添加 --debug 修饰符;如果使用 web 运行器来显示输出,可以添加 &debug=1 到网址中
- 在命令行运行使用会话的测试时,需要加上 --stderr 标志。不这么做会导致会话无法 工作。PHPUnit 默认会输出测试进程到标准输出(stdout),这会使 PHP 以为头部信息 已经发送,从而阻止会话启动。把 PHPUnit 输出切换到 stderr,就避免了这个问题
步骤
- 安装PHPUnit
使用composer安装方法,"phpunit/phpunit": "3.7.32"
- (可选)测试数据库的设置,在database.php中添加$test变量
- 在
webroot
目录有test.php
的前提下,可以直接访问localhost/appname/webroot/test.php
,如有配置虚拟域名,可能是xx.com/test.php
-
测试用例存放目录在
/Test/Case
中,文件名有要求:必须以Test.php
结尾,如UserTest.php
,含有测试的类应当扩展 CakeTestCase,ControllerTestCase 或 PHPUnit_Framework_TestCase,所以类名(与文件名对应)示例class UserTest extends CakeTestCase
,方法名要求是以test
开头,如public function testPublished()
- 可以使用web访问的方式测试,也可以用命令行,
./Console/cake test app Model/Post
,这里的cake命令是在Console目录下的,与执行shell脚本的命令一样,test和app两个参数不需要改变,Model/Post
指向的是/Test/Case/Model/PostTest.php
- 以下一则测试的例子:
<?php App::uses('Ad', 'Model'); class AdTest extends CakeTestCase { protected $Ad; public function setUp() { parent::setUp(); $this->Article = ClassRegistry::init('Article'); } public function testA() { $this->assertEquals(1, 1); } public function testB() { $ads = $this->Ad->getRunningAds(); var_dump($ads); $this->assertEquals($ads[4397]['banner'][0]['axid'], 4397); } public function testC() { $ads = $this->Ad->getRunningAds(); var_dump($ads); $this->assertEquals($ads[4397]['banner'][0]['axid'], 4398); } } /* 测试用例有一些生命周期回调函数,可以在测试时使用: setUp 在每个测试方法之前调用。应当用来创建要测试的对象,为测试初始化任何 数据。记得一定要调用 parent::setUp()。 tearDown 在每个测试方法之后调用。应当用来在测试完成之后进行清理。记得一定 要调用 parent::tearDown()。 setupBeforeClass 在一个用例中的测试方法开始之前只调用一次。该方法必须是 静态的。 tearDownAfterClass 在一个用例中的测试方法完成之后只调用一次。该方法必须是 静态的。 */
- 覆盖率检测:使用
xdebug
工具,PHPUnit会结合工具检测出测试代码的盲区(无关代码不会影响覆盖率的计算,如测试的是方法A,在运行的过程中没有经过方法B,并不会影响覆盖率;但是方法A中的if语句,只走了其中一个条件,则会提示未覆盖)
xdebug安装方法
php-xdebug扩展:composer安装方法"ext-xdebug": ">=2.0.5"
测试模型
App::uses('Article', 'Model');
class ArticleTest extends CakeTestCase {
public $fixtures = array('app.article');
public function setUp() {
parent::setUp();
// 在为测试设置模型时,使用 ClassRegistry::init('YourModelName')的原因是它知道要使用测试数据库连接(如果不使用测试数据库和夹具,直接new也可以)
$this->Article = ClassRegistry::init('Article');
}
public function testPublished() {
// Article模型中有一个published方法
$result = $this->Article->published(array('id', 'title'));
$expected = array(
array('Article' => array('id' => 1, 'title' => 'First Article')),
array('Article' => array('id' => 2, 'title' => 'Second Article')),
array('Article' => array('id' => 3, 'title' => 'Third Article'))
);
$this->assertEquals($expected, $result);
}
}
测试控制器
CakePHP 提供了特别的 ControllerTestCase 类。用该类作为控制器测试 用例的基类,让你可以使用 testAction() 方法,使测试用例更简单。 ControllerTestCase 让你容易地模拟组件和模型,以及象 redirect() 这样可能更难测试的方法
class ArticlesControllerTest extends ControllerTestCase {
public $fixtures = array('app.article');
public function testIndex() {
$result = $this->testAction('/articles/index');
debug($result);
}
public function testIndexShort() {
$result = $this->testAction('/articles/index/short');
debug($result);
}
public function testIndexShortGetRenderedHtml() {
$result = $this->testAction(
'/articles/index/short',
array('return' => 'contents')
);
debug($result);
}
public function testIndexShortGetViewVars() {
$result = $this->testAction(
'/articles/index/short',
array('return' => 'vars')
);
debug($result);
}
public function testIndexPostData() {
$data = array(
'Article' => array(
'user_id' => 1,
'published' => 1,
'slug' => 'new-article',
'title' => 'New Article',
'body' => 'New Body'
)
);
$result = $this->testAction(
'/articles/index',
array('data' => $data, 'method' => 'post')
);
debug($result);
}
}
testAction 方法的 第一个参数应当总是要测试的网址(URL)。CakePHP 会创建一个请求,调度(dispatch) 控制器和动作
在测试包含 redirect() 方法和其它在重定向(redirect)之后的代码,通常更好的 做法是在重定向时返回。这是因为,redirect() 方法在测试中是模拟的,并不像正常 状态是存在的。它不会使代码退出,而是继续运行重定向之后的代码
App::uses('AppController', 'Controller');
class ArticlesController extends AppController {
public function add() {
if ($this->request->is('post')) {
if ($this->Article->save($this->request->data)) {
// 如果没有return,测试代码会继续执行下去
return $this->redirect(array('action' => 'index'));
}
}
// 更多代码
}
}
第二个参数是传递的请求数据和方法
public function testAdding() {
$data = array(
'Post' => array(
'title' => 'New post',
'body' => 'Secret sauce'
)
);
// 默认是post,如果get方法,则特别指定
$this->testAction('/posts/add', array('data' => $data, 'method' => 'get'));
// 一些断言(*assertion*)。
}
选择返回类型
- vars 得到设置的视图(view)变量。
- view 得到渲染的不含布局(layout)的视图。
- contents 得到渲染的包含布局(layout)的视图。
- result 得到控制器动作的返回值。可用于测试 requestAction 方法。
默认值为 result。只要返回类型不是 result,也可以在测试用例中用属性访问 其它返回类型:
public function testIndex() {
$this->testAction('/posts/index');
$this->assertInternalType('array', $this->vars['posts']);
}
测试返回 JSON 响应的控制器
class MarkersControllerTest extends ControllerTestCase {
public function testIndex() {
$result = $this->testAction('/markers/index.json');
$result = json_decode($result, true);
$expected = array(
'Marker' => array('id' => 1, 'lng' => 66, 'lat' => 45),
);
$this->assertEquals($expected, $result);
}
}
测试视图
通常大部分应用程序不会直接测试它们的 HTML 代码。这么做经常会导致脆弱、难以维护的 测试套件,容易遭到破坏。在使用 ControllerTestCase 编写功能性测试时, 可以设置 return 选项为 ‘view’ 来检视渲染的视图内容
创建测试套件
如果你想要几个测试一起运行,可以创建测试套件。一个测试套件由多个测试用例组成
如果想要为所有的模型测试创建测试套件,可以创建app/Test/Case/AllModelTest.php
:
class AllModelTest extends CakeTestSuite {
public static function suite() {
$suite = new CakeTestSuite('All model tests');
$suite->addTestDirectory(TESTS . 'Case/Model');
return $suite;
}
}
以上代码会把目录 /app/Test/Case/Model/
中所有的测试用例组织在一起。要添加 单个文件,使用 $suite->addTestFile($filename);
方法
递归添加一个目录中的所有测试或测试套件:$suite->addTestDirectoryRecursive(TESTS . 'Case/Model');
或 $suite->addTestDirectoryRecursive(TESTS . 'Case');
网友评论