这是一个系列文章,分3部分:
1、准备工作--Yii2高级模板的安装,以及编写一个类(方法)和一个model的RETful
2、测试工作--单元测试和API(REST规范的)测试
3、力争上游--覆盖率、基础概念、引用文献等
本文是第二部分,主要是单元测试和API测试。
单元测试
1、执行vendor/bin/codecept generate:cest -c common unit CustomeString
,
新建了common\tests\unit\CustomeStringCest.php
,开始写测试代码了,增加内容:
public function tryToTest(UnitTester $I)
{
$len = 10;
$prefix = 'pre';
$string = CustomString::generateCode($len, $prefix);
expect(strlen($string))->equals($len + 3);
expect(substr($string, 0, 3))->equals($prefix);
//等效于 expect($string)->startsWith($prefix);
//这就是codeception的强大之处,封装了几乎所有测试需要的,官方说90%+,且不希望你重复造轮子
}
2、执行vendor/bin/codecept run -c common
。
我们可以在命令终端看到测试运行的情况。
至此,恭喜你,已经利用codeception进行了某个类的(方法的)测试。
我们继续来书写如何测试一个model:
4、执行vendor/bin/codecept generate:test -c common unit models/News
新建一个common\test\unit\models\NewsTest.php,我们仿照LoginFormTest.php
,编写NewsTest.php:
<?php
namespace common\tests\models;
use common\fixtures\NewsFixture;
use common\models\News;
class NewsTest extends \Codeception\Test\Unit
{
public function _fixtures()
{
return [
'news' => [
'class' => NewsFixture::class,
'dataFile' => codecept_data_dir() . 'news.php',
],
];
}
public function testValidate()
{
$model = new News(['title' => 'one title']);
expect('model should create successfully', $model->save())->true();
$model = new News(['code' => \Yii::$app->security->generateRandomString(33)]);
expect('model should create fail', $model->save())->false();
expect('error message should be set', $model->errors)->hasKey('code');
$model = new News(['title' => null]);
expect('model should create fail', $model->save())->false();
expect('error message should be set', $model->errors)->hasKey('title');
$model = new News(['title' => \Yii::$app->security->generateRandomString(256)]);
expect('model should create fail', $model->save())->false();
expect('error message should be set', $model->errors)->hasKey('title');
}
public function testData()
{
$data = News::find()->where(['title' => 'This is a title'])->one();
expect(count($data))->equals(1);
}
}
5、因为需要用到NewsFixture::class
和codecept_data_dir() . 'news.php'
,
所以我们需要在common的fixtures下新建NewFixture.php
(具体位置无所谓,只需要保证命名空间是common\fixtures
):
<?php
namespace common\fixtures;
use yii\test\ActiveFixture;
class NewsFixture extends ActiveFixture
{
public $modelClass = 'common\models\News';
}
然后在common\tests\_data
下新建news.php,自己添加一些数据:
<?php
return [
[
'code' => 'abcdefg',
'title' => 'This is a title',
'content' => '<p>some Content</p>',
'status' => 0,
'created_at' => 1402312317,
'updated_at' => 1402312317,
'created_by' => 1,
'updated_by' => 1,
],
[
'code' => '22222',
'title' => 'This is second title',
'content' => '<p>some Content</p>',
'status' => 1,
'created_at' => 1402312317,
'updated_at' => 1402312317,
'created_by' => 1,
'updated_by' => 1,
],
];
6、执行vendor/bin/codecept run -c common
,查看测试运行情况。
至此,单元测试算告一段落。
单元测试,基本上就是对自定义的类的(方法的)测试,还有model的一些测试。
当有新的自定义类或者model时,类似上述新增测试用例即可。
7、 补充:
由于codeception包含3种格式的测试文件:Cept, Cest and Test
Cept are written as narrative scenarios. To make a PHP file a valid scenario, its name should have a Cept suffix.
描述行语言,文件后缀是Cept,常用于功能、验收还有API测试。
Cest combines scenario-driven test approach with OOP design. In case you want to group a few testing scenarios into one, you should consider using Cest format.
Codeception can also execute PHPUnit test files for unit testing。
注意Test格式时,方法名需要以test
开头,另外两种格式不要求。
上述的例子是Test格式,当然可以写成Cept和Cest,实质上等效。
我习惯单元测试用Cest,API测试用Cept。
执行vendor/bin/codecept generate:cest -c common unit models/News
,
新建一个common\test\unit\models\NewsCest.php,等效代码如下:
<?php
namespace common\tests\unit\models;
use common\fixtures\NewsFixture;
use common\models\News;
use common\tests\UnitTester;
class NewsCest
{
public function _before(UnitTester $I)
{
$I->haveFixtures([
'news' => [
'class' => NewsFixture::class,
'dataFile' => codecept_data_dir() . 'news.php',
],
]);
}
public function _after(UnitTester $I)
{
}
// tests
public function tryToTestValidate(UnitTester $I)
{
$model = new News(['title' => 'one title']);
expect('model should create successfully', $model->save())->true();
$model = new News(['code' => \Yii::$app->security->generateRandomString(33)]);
expect('model should create fail', $model->save())->false();
expect('error message should be set', $model->errors)->hasKey('code');
$model = new News(['title' => null]);
expect('model should create fail', $model->save())->false();
expect('error message should be set', $model->errors)->hasKey('title');
$model = new News(['title' => \Yii::$app->security->generateRandomString(256)]);
expect('model should create fail', $model->save())->false();
expect('error message should be set', $model->errors)->hasKey('title');
}
public function toTestData()
{
$data = News::find()->where(['title' => 'This is a title'])->one();
expect(count($data))->equals(1);
}
}
功能和验收测试
由于前后端分离,这里不再介绍codeception的功能和验收测试。
你可以查看frontend/tests/下的functional和accptance部分。
关于前端测试,可以参看CodeceptJS
API测试
接口的自动化测试,这里是一个方案,具体参看WebServices,
Postman的自动化接口测试也是一个不错的方案,有时间再整理。
首先要执行vendor/bin/codecept g:suite api -c blog
,输出:
Helper \blog\tests\Helper\Api was created in /home/lijun/works/yii-application/blog/tests/_support/Helper/Api.php
Actor ApiTester was created in /home/lijun/works/yii-application/blog/tests/_support/ApiTester.php
Suite config api.suite.yml was created.
Next steps:
1. Edit api.suite.yml to enable modules for this suite
2. Create first test with generate:cest testName ( or test|cept) command
3. Run tests of this suite with codecept run api command
Suite api generated
按照提示继续下面的步骤:
1)、编辑api.suite.yml,启用REST,且依赖Yii2。如下:
#suite_namespace: blog\tests\api
actor: ApiTester
modules:
enabled:
- \blog\tests\Helper\Api
- REST:
#url:
depends: Yii2
#part: Json
2)、执行vendor/bin/codecept g:cept api -c blog News
创建测试文件blog\tests\api\NewsCept.php。
<?php
use blog\tests\ApiTester;
use Codeception\Util\HttpCode;
$I = new ApiTester($scenario);
//create
$data = ['code' => 'code1', 'title' => 'first title', 'content' => 'first content'];
$I->wantTo('create a news');
$I->sendPOST('/news', $data);
$I->seeResponseCodeIs(HttpCode::CREATED);
$I->seeResponseIsJson();
//{"code":"code1","title":"first title","content":"first content","id":16}
$I->seeResponseContainsJson($data);
//view
$I->wantTo('view a news');
$id = $I->grabDataFromResponseByJsonPath('$.id')[0];
$I->sendGET('/news/' . $id);
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType([
'id' => 'integer',
'title' => 'string',
'content' => 'string|null',
]);
//update
$I->wantTo('update news');
$id = $I->grabDataFromResponseByJsonPath('$.id')[0];
$I->sendPUT('/news/' . $id, ['content' => 'updated content']);
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeResponseContainsJson(['content' => 'updated content']);
//index
$I->wantTo('list news');
$I->sendGET('/news');
$I->seeResponseCodeIs(HttpCode::OK);
//{"items":[{"id":49,"code":"code1","title":"first title","content":"first content","status":0,"created_at":0,"created_by":0,"updated_at":0,"updated_by":0}],"_links":{"self":{"href":"http://localhost/index-test.php/news?page=1"}},"_meta":{"totalCount":1,"pageCount":1,"currentPage":1,"perPage":20}}
$I->seeResponseJsonMatchesJsonPath('$.items..title');
$I->seeResponseJsonMatchesXpath('//_meta//totalCount');
//delete
$I->wantTo('delete a news');
$id = $I->grabDataFromResponseByJsonPath('$.items..id');
$I->sendDELETE('/news/' . $id[0]);
$I->seeResponseCodeIs(HttpCode::NO_CONTENT);
//index
$I->wantTo('list empty news');
$I->sendGET('/news');
$I->seeResponseCodeIs(HttpCode::OK);
3)、运行测试vendor/bin/codecept run -c blog
,
提示需要安装flow/jsonpath
,执行composer require -dev "flow/jsonpath"
。待安装之后再次执行测试即可。
至此,REST的API测试完成。
Yii2下使用codeception进行单元测试和API测试到此告一段落,后面可以继续熟悉codeception的强大吧。
网友评论