使用Codeception测试应用
默认情况下,基础和高级Yii2应用skeletons使用Codeception作为一个测试框架。Codeception支持写单元,函数,以及接受box之外的测试。对于单元测试,它使用PHPUnit测试框架,它将被在下个小节中讨论。
准备
- 按照官方指南http://www.yiiframework.com/doc-2.0/guide-start-installation.html的描述,使用Composer包管理器创建一个新
yii2-app-basic的应用。
注意:如果你用的是基础应用的2.0.9版本(或者更早),只需要手动升级tests文件夹,并添加config/test.php,config/test_db.php和web/index-test.php文件。此外你需要复制composer.json文件的require和require-dev部分,并运行composer update。
- 复制并应用如下migration:
<?phpuse yii\db\Migration;class m160309_070856_create_post extends Migration{public function up(){$this->createTable('{{%post}}', ['id' => $this->primaryKey(),'title' => $this->string()->notNull(),'text' => $this->text()->notNull(),'status' => $this->smallInteger()->notNull()->defaultValue(0),]);}public function down(){$this->dropTable('{{%post}}');}}
- 创建
Post模型:
<?phpnamespace app\models;use Yii;use yii\db\ActiveRecord;/*** @property integer $id* @property string $title* @property string $text* @property integer $status* @property integer $created_at* @property integer $updated_at*/class Post extends ActiveRecord{const STATUS_DRAFT = 0;const STATUS_ACTIVE = 1;public static function tableName(){return '{{%post}}';}public function rules(){return [[['title', 'text'], 'required'],[['text'], 'string'],['status', 'in', 'range' => [self::STATUS_DRAFT,self::STATUS_ACTIVE]],['status', 'default', 'value' =>self::STATUS_DRAFT],[['title'], 'string', 'max' => 255],];}public function behaviors(){return [TimestampBehavior::className(),];}public static function getStatusList(){return [self::STATUS_DRAFT => 'Draft',self::STATUS_ACTIVE => 'Active',];}public function publish(){if ($this->status == self::STATUS_ACTIVE) {throw new \DomainException('Post is already published.');}$this->status = self::STATUS_ACTIVE;}public function draft(){if ($this->status == self::STATUS_DRAFT) {throw new \DomainException('Post is already drafted.');}$this->status = self::STATUS_DRAFT;}}
- 生成CRUD:

- 此外,在
views/admin/posts/_form.php文件中为status字段添加状态下拉菜单,以及提交按钮的名称:
<div class="post-form"><?php $form = ActiveForm::begin(); ?><?= $form->field($model, 'title')->textInput(['maxlength'=> true]) ?><?= $form->field($model, 'text')->textarea(['rows' => 6]) ?><?= $form->field($model, 'status')->dropDownList(Post::getStatusList()) ?><div class="form-group"><?= Html::submitButton($model->isNewRecord ? 'Create' :'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary','name' => 'submit-button',]) ?></div><?php ActiveForm::end(); ?></div>
- 现在检查控制器是否工作:

创建任意示例帖子。
如何做…
为测试做准备
跟着如下步骤来为测试做准备:
- 创建
yii2_basic_tests或者其它测试数据库,通过应用migration来更新它:
tests/bin/yii migrate
这个命令需要在测试文件夹中运行。你可以在配置文件/config/test_db.php中指定你的测试数据库选项。
- Codeception为我们的测试套件使用自动生成的Actor类。使用下面的命令创建他们:
composer exec codecept build
运行单元和功能测试
我们可以立即运行任何类型的应用测试:
# run all available testscomposer exec codecept run# run functional testscomposer exec codecept run functional# run unit testscomposer exec codecept run unit
结果是,你可以查看测试报告如下所示:

获取覆盖报告
你可以为你的代码获取代码覆盖率报告。默认情况下,代码覆盖率在配置文件tests/codeception.yml中是禁用的;你需要取消必要的注释,从而可以收集代码覆盖率:
coverage:enabled: truewhitelist:include:- models/*- controllers/*- commands/*- mail/*blacklist:include:- assets/*- config/*- runtime/*- vendor/*- views/*- web/*- tests/*
你需要安装XDebug PHP扩展,这可以从https://xdebug.org获取。例如,在Ubuntu或者Debian上,你可以在控制台中输入如下命令:
sudo apt-get install php5-xdebug
在Windows上,你必须打开php.ini文件,并添加到你PHP安装路径的自定义代码:
[xdebug]zend_extension_ts=C:/php/ext/php_xdebug.dll
或者,如果你使用的是非线程安全的版本,输入如下命令:
[xdebug]zend_extension=C:/php/ext/php_xdebug.dll
最后,你可以运行测试,并使用如下命令收集覆盖率报告:
#collect coverage for all testscomposer exec codecept run --coverage-html#collect coverage only for unit testscomposer exec codecept run unit --coverage-html#collect coverage for unit and functional testscomposer exec codecept run functional,unit --coverage-html
你可以在终端上看到文本的代码覆盖率输出:
Code Coverage Report:2016-03-31 08:13:05Summary:Classes: 20.00% (1/5)Methods: 40.91% (9/22)Lines: 30.65% (38/124)\app\models::ContactFormMethods: 33.33% ( 1/ 3) Lines: 80.00% ( 12/ 15)\app\models::LoginFormMethods: 100.00% ( 4/ 4) Lines: 100.00% ( 18/ 18)\app\models::UserMethods: 57.14% ( 4/ 7) Lines: 53.33% ( 8/ 15)Remote CodeCoverage reports are not printed to consoleHTML report generated in coverage
此外,你可以在tests/codeception/_output/coverage路径中看到HTML格式的报告:

你可以点击任何类,并分析代码的哪些行在测试运行过程中还没有被执行到。
运行验收测试
在验收测试中,你可以使用基于Curl的PhpBrowser作为请求服务器。它可以帮助你检查网站控制器,并解析HTTP和HTML响应代码。但是如果你希望测试你的CSS或者JavaScript行为,你必须使用真正的浏览器。
Selenium服务器是一个交互式的工具,它集成到了Firefox以及其它浏览器中,可以让你打开站点页面,并模拟人类动作。
为了使用真正的浏览器,我们必须安装Selenium服务器:
- 需要完整版的Codeception包,而不是基础版的:
composer require --dev codeception/codeceptioncomposer remove --dev codeception/base
- 下载如下软件:
- 安装Mozilla Firefox浏览器,https://www.mozilla.org
- 安装jre(Java运行时环境),https://www.java.com/en/download/
- 下载Selenium独立服务器,http://www.seleniumhq.org/download/
- 下载Geckodriver,https://github.com/mozilla/geckodriver/releases
- 在新的控制台窗口中启动带有driver的服务器:
java -jar -Dwebdriver.gecko.driver=~/geckodriver ~/selenium-server-standalone-x.xx.x.jar
- 复制
tests/acceptance.suite.yml.example到tests/acceptance.suite.yml,并按如下配置:
class_name: AcceptanceTestermodules:enabled:- WebDriver:url: http://127.0.0.1:8080/browser: firefox- Yii2:part: ormentryScript: index-test.phpcleanup: false
- 打开新的终端,并启动web服务器:
tests/bin/yii serve
- 运行验收测试:
composer exec codecept run acceptance
你应该能看到Selenium是如何启动浏览器的,并检查所有的站点页面。
创建数据库fixtures
在运行自己的测试之前,我们必须清楚自己的测试数据库,并加载指定的测试数据。yii2-codeception扩展提供ActiveFixture基类,用于为自己的模型创建测试数据集。运行如下步骤创建数据库fixtures:
- 为
Post模型创建fixture类:
<?phpnamespace tests\fixtures;use yii\test\ActiveFixture;class PostFixture extends ActiveFixture{public $modelClass = 'app\modules\Post';public $dataFile = '@tests/_data/post.php';}
- 在
test/_data/post.php文件中添加一个演示数据集:
<?phpreturn [['id' => 1,'title' => 'First Post','text' => 'First Post Text','status' => 1,'created_at' => 1457211600,'updated_at' => 1457211600,],['id' => 2,'title' => 'Old Title For Updating','text' => 'Old Text For Updating','status' => 1,'created_at' => 1457211600,'updated_at' => 1457211600,],['id' => 3,'title' => 'Title For Deleting','text' => 'Text For Deleting','status' => 1,'created_at' => 1457211600,'updated_at' => 1457211600,],];
- 为单元测试和验收测试激活fixtures支持。只需要添加
fixtures部分到unit.suite.yml文件中:
class_name: UnitTestermodules:enabled:- Asserts- Yii2:part: [orm, fixtures, email]
此外,添加fixtures部分到acceptance.suite.yml文件中:
class_name: AcceptanceTestermodules:enabled:- WebDriver:url: http://127.0.0.1:8080/browser: firefox- Yii2:part: [orm, fixtures]entryScript: index-test.phpcleanup: false
- 运行如下命令,重新生成
tester类,应用这些修改:
composer exec codecept build
写单元或综合测试
单元和综合测试检查我们项目的源代码。
单元测试只检查当前的类,或者他们的方法和其它类无关,以及和其它资源也无关,比如数据库、文件等等。
综合测试检查你的类和其它类以及资源综合在一起时的表现。
Yii2中的ActiveRecord模型总是使用数据库加载表schema,所以我们必须创建一个真正的测试数据库,并且我们的测试将会是综合的。
- 写测试用于检查模型的校验、保存,并修改它的状态:
<?phpnamespace tests\unit\models;use app\models\Post;use Codeception\Test\Unit;use tests\fixtures\PostFixture;class PostTest extends Unit{/*** @var \UnitTester*/protected $tester;public function _before(){$this->tester->haveFixtures(['post' => ['class' => PostFixture::className(),'dataFile' => codecept_data_dir() . 'post.php']]);}public function testValidateEmpty(){$model = new Post();expect('model should not validate',$model->validate())->false();expect('title has error',$model->errors)->hasKey('title');expect('title has error',$model->errors)->hasKey('text');}public function testValidateCorrect(){$model = new Post(['title' => 'Other Post','text' => 'Other Post Text',]);expect('model should validate',$model->validate())->true();}public function testSave(){$model = new Post(['title' => 'Test Post','text' => 'Test Post Text',]);expect('model should save', $model->save())->true();expect('title is correct', $model->title)->equals('Test Post');expect('text is correct', $model->text)->equals('Test Post Text');expect('status is draft',$model->status)->equals(Post::STATUS_DRAFT);expect('created_at is generated',$model->created_at)->notEmpty();expect('updated_at is generated',$model->updated_at)->notEmpty();}public function testPublish(){$model = new Post(['status' => Post::STATUS_DRAFT]);expect('post is drafted',$model->status)->equals(Post::STATUS_DRAFT);$model->publish();expect('post is published',$model->status)->equals(Post::STATUS_ACTIVE);}public function testAlreadyPublished(){$model = new Post(['status' => Post::STATUS_ACTIVE]);$this->setExpectedException('\LogicException');$model->publish();}public function testDraft(){$model = new Post(['status' => Post::STATUS_ACTIVE]);expect('post is published',$model->status)->equals(Post::STATUS_ACTIVE);$model->draft();expect('post is drafted',$model->status)->equals(Post::STATUS_DRAFT);}public function testAlreadyDrafted(){$model = new Post(['status' => Post::STATUS_ACTIVE]);$this->setExpectedException('\LogicException');$model->publish();}}
- 运行测试:
composer exec codecept run unit
- 现在查看结果:

完成了。如果你故意或者偶然破坏了任何模型的方法,你将会看到一个坏的测试。
写功能测试
功能测试检查你的应用是否正常工作。这个套件会准备$_GET、$_POST以及其它请求变量,并调用Application::handleRequest方法。它帮助你测试控制器以及响应,而且这不需要运行真实服务器。
现在我们可以为我们的admin CRUD写测试:
- 生成一个新的测试类:
codecept generate:cest functional admin/Posts
- 在生成的文件中修正命名空间,并写自己的测试:
<?phpnamespace tests\functional\admin;use app\models\Post;use FunctionalTester;use tests\fixtures\PostFixture;use yii\helpers\Url;class PostsCest{function _before(FunctionalTester $I){$I->haveFixtures(['user' => ['class' => PostFixture::className(),'dataFile' => codecept_data_dir() . 'post.php']]);}public function testIndex(FunctionalTester $I){$I->amOnPage(['admin/posts/index']);$I->see('Posts', 'h1');}public function testView(FunctionalTester $I){$I->amOnPage(['admin/posts/view', 'id' => 1]);$I->see('First Post', 'h1');}public function testCreateInvalid(FunctionalTester $I){$I->amOnPage(['admin/posts/create']);$I->see('Create', 'h1');$I->submitForm('#post-form', ['Post[title]' => '','Post[text]' => '',]);$I->expectTo('see validation errors');$I->see('Title cannot be blank.', '.help-block');$I->see('Text cannot be blank.', '.help-block');}public function testCreateValid(FunctionalTester $I){$I->amOnPage(['admin/posts/create']);$I->see('Create', 'h1');$I->submitForm('#post-form', ['Post[title]' => 'Post Create Title','Post[text]' => 'Post Create Text','Post[status]' => 'Active',]);$I->expectTo('see view page');$I->see('Post Create Title', 'h1');}public function testUpdate(FunctionalTester $I){// ...}public function testDelete(FunctionalTester $I){$I->amOnPage(['/admin/posts/view', 'id' => 3]);$I->see('Title For Deleting', 'h1');$I->amGoingTo('delete item');$I->sendAjaxPostRequest(Url::to(['/admin/posts/delete','id' => 3]));$I->expectTo('see that post is deleted');$I->dontSeeRecord(Post::className(), ['title' => 'Title For Deleting',]);}}
- 运行测试命令:
composer exec codecept run functional
- 现在可以查看到结果:

所有的测试是通过的。在其它例子中,你可以在tests/_output文件夹中看到失败测试的测试页面的截图:
写验收测试
- 验收测试员点击测试服务器中真正的网站,而不是只调用
Application::handleRequest方法。高级验收测试看起来像中级功能测试,但是在Selenium中它可以检查在真实浏览器中的Javascript行为。 - 你必须在
tests/acceptance文件夹下获取如下类:
<?phpnamespace tests\acceptance\admin;use AcceptanceTester;use tests\fixtures\PostFixture;use yii\helpers\Url;class PostsCest{function _before(AcceptanceTester $I){$I->haveFixtures(['post' => ['class' => PostFixture::className(),'dataFile' => codecept_data_dir() . 'post.php']]);}public function testIndex(AcceptanceTester $I){$I->wantTo('ensure that post index page works');$I->amOnPage(Url::to(['/admin/posts/index']));$I->see('Posts', 'h1');}public function testView(AcceptanceTester $I){$I->wantTo('ensure that post view page works');$I->amOnPage(Url::to(['/admin/posts/view', 'id' => 1]));$I->see('First Post', 'h1');}public function testCreate(AcceptanceTester $I){$I->wantTo('ensure that post create page works');$I->amOnPage(Url::to(['/admin/posts/create']));$I->see('Create', 'h1');$I->fillField('#post-title', 'Post Create Title');$I->fillField('#post-text', 'Post Create Text');$I->selectOption('#post-status', 'Active');$I->click('submit-button');$I->wait(3);$I->expectTo('see view page');$I->see('Post Create Title', 'h1');}public function testDelete(AcceptanceTester $I){$I->amOnPage(Url::to(['/admin/posts/view', 'id' => 3]));$I->see('Title For Deleting', 'h1');$I->click('Delete');$I->acceptPopup();$I->wait(3);$I->see('Posts', 'h1');}}
不要忘记调用wait方法,用于等待页面被打开或者重新加载。
- 在一个新的终端中运行PHP测试服务器:
tests/bin/yii serve
- 运行验收测试:
composer exec codecept run acceptance
- 查看结果:

Selenium将会启动Firefox web浏览器,并执行我们的测试命令。
创建API测试套件
除了单元、功能以及验收套件,Codeception可以创建特殊的测试套件。例如,我们可以创建用于支持XML和JSON解析的API测试。
- 为
Post模型创建REST API控制器controllers/api/PostsController.php
<?phpnamespace app\controllers\api;use yii\rest\ActiveController;class PostsController extends ActiveController{public $modelClass = '\app\models\Post';}
- 在
config/web.php中为urlManager组件添加REST路由:
'components' => [// ...'urlManager' => ['enablePrettyUrl' => true,'showScriptName' => false,'rules' => [['class' => 'yii\rest\UrlRule', 'controller' => 'api/posts'],],],],
并在config/test.php文件中设置一些配置(启用showScriptName选项):
'components' => [// ...'urlManager' => ['enablePrettyUrl' => true,'showScriptName' => true,'rules' => [['class' => 'yii\rest\UrlRule', 'controller' => 'api/posts'],],],],
- 给
web/.htaccess文件添加如下内容:
RewriteEngine OnRewriteCond %{REQUEST_FILENAME} !-fRewriteCond %{REQUEST_FILENAME} !-dRewriteRule . index.php
- 检查
api/posts控制器是否工作:

- 使用REST模块创建API测试套件
tests/api.suite.yml配置文件:
class_name: ApiTestermodules:enabled:- REST:depends: PhpBrowserurl: 'http://127.0.0.1:8080/index-test.php'part: [json]- Yii2:part: [orm, fixtures]entryScript: index-test.php
现在重新编译testers:
composer exec codecept build
- 创建
tests/api目录,并生成新的测试类:
composer exec codecept generate:cest api Posts
- 为你的REST-API写测试:
<?phpnamespace tests\api;use ApiTester;use tests\fixtures\PostFixture;use yii\helpers\Url;class PostsCest{function _before(ApiTester $I){$I->haveFixtures(['post' => ['class' => PostFixture::className(),'dataFile' => codecept_data_dir() . 'post.php']]);}public function testGetAll(ApiTester $I){$I->sendGET('/api/posts');$I->seeResponseCodeIs(200);$I->seeResponseIsJson();$I->seeResponseContainsJson([0 => ['title' => 'First Post']]);}public function testGetOne(ApiTester $I){$I->sendGET('/api/posts/1');$I->seeResponseCodeIs(200);$I->seeResponseIsJson();$I->seeResponseContainsJson(['title' => 'First Post']);}public function testGetNotFound(ApiTester $I){$I->sendGET('/api/posts/100');$I->seeResponseCodeIs(404);$I->seeResponseIsJson();$I->seeResponseContainsJson(['name' => 'Not Found']);}public function testCreate(ApiTester $I){$I->sendPOST('/api/posts', ['title' => 'Test Title','text' => 'Test Text',]);$I->seeResponseCodeIs(201);$I->seeResponseIsJson();$I->seeResponseContainsJson(['title' => 'Test Title']);}public function testUpdate(ApiTester $I){$I->sendPUT('/api/posts/2', ['title' => 'New Title',]);$I->seeResponseCodeIs(200);$I->seeResponseIsJson();$I->seeResponseContainsJson(['title' => 'New Title','text' => 'Old Text For Updating',]);}public function testDelete(ApiTester $I){$I->sendDELETE('/api/posts/3');$I->seeResponseCodeIs(204);}}
- 运行应用服务器:
tests/bin yii serve
- 运行API测试:
composer exec codecept run api
现在可以看到结果:

所有的测试通过了,我们的API能正常工作。
工作原理…
Codeception是一个高级测试框架,基于PHPUnit包可以提供用于写单元、综合、功能和验收测试。
我们可以使用Yii2的内置Codeception,它允许我们加载fixtures,使用models,以及Yii框架的其它东西。
参考
- 欲了解更多信息,参考:
- 基础或者高级应用中的
tests/README.md文件: - 使用PHPUnit做单元测试小节
