基于 yiisoft/yii2-app-advanced,在 GitHub 上新建仓库 yii2-app-advanced,新建接口应用(实现 RESTful 风格的 Web Service 服务的 API),在 api 的 tests 目录中准备用户相关操作的一些自动化测试的样例(API 测试),确保应用程序在改变或增加新的功能时不会影响现有的功能 (四)

1、删除控制器 \api\controllers\SiteController.php,删除模型 \api\models\ContactForm.php、\api\models\LoginForm.php、\api\models\PasswordResetRequestForm.php、\api\models\ResetPasswordForm.php、\api\models\SignupForm.php,删除目录(视图相关) \api\views、\api\assets、\api\web\css

2、删除测试(单元测试、功能测试、验收测试)的相关文件,删除 \api\tests\acceptance\HomeCest.php,删除 \api\tests\functional\AboutCest.php、\api\tests\functional\ContactCest.php、\api\tests\functional\HomeCest.php、\api\tests\functional\LoginCest.php、\api\tests\functional\SignupCest.php,删除 \api\tests\unit\models\ContactFormTest.php、\api\tests\unit\models\PasswordResetRequestFormTest.php、\api\tests\unit\models\ResetPasswordFormTest.php、\api\tests\unit\models\SignupFormTest.php

3、运行所有的样例测试,报错:FAILURES! Tests: 24, Assertions: 57, Failures: 5,如图1

图1

Codeception PHP Testing Framework v2.4.1
Powered by PHPUnit 7.0.3 by Sebastian Bergmann and contributors.

Frontend\tests.functional Tests (12) -------------------------------------------------------------------------------------------------------------------------
+ AboutCest: Check about (0.10s)
+ ContactCest: Check contact (0.19s)
x ContactCest: Check contact submit no data (0.04s)
x ContactCest: Check contact submit not correct email (0.02s)
+ ContactCest: Check contact submit correct data (0.08s)
+ HomeCest: Check open (0.01s)
x LoginCest: Check empty (0.02s)
+ LoginCest: Check wrong password (0.03s)
+ LoginCest: Check valid login (0.58s)
x SignupCest: Signup with empty fields (0.02s)
x SignupCest: Signup with wrong email (0.03s)
+ SignupCest: Signup successfully (0.59s)
--------------------------------------------------------------------------------------------------------------------------------------------------------------

Frontend\tests.unit Tests (8) --------------------------------------------------------------------------------------------------------------------------------
+ ContactFormTest: Send email (0.02s)
+ PasswordResetRequestFormTest: Send message with wrong email address (0.03s)
+ PasswordResetRequestFormTest: Not send emails to inactive user (0.03s)
+ PasswordResetRequestFormTest: Send email successfully (0.04s)
+ ResetPasswordFormTest: Reset wrong token (0.03s)
+ ResetPasswordFormTest: Reset correct token (0.59s)
+ SignupFormTest: Correct signup (1.14s)
+ SignupFormTest: Not correct signup (0.03s)
--------------------------------------------------------------------------------------------------------------------------------------------------------------


Time: 4.83 seconds, Memory: 26.00MB

There were 5 failures:

---------
1) ContactCest: Check contact submit no data
 Test  tests\functional\ContactCest.php:checkContactSubmitNoData
 Step  See "Name cannot be blank",".help-block"
 Fail  Failed asserting that any element by '.help-block' on user /index-test.php/site/contact
+ <p class="help-block help-block-error">Name不能为空。</p>
+ <p class="help-block help-block-error">Email不能为空。</p>
+ <p class="help-block help-block-error">Subject不能为空。</p>
+ <p class="help-block help-block-error">Body不能为空。</p>
+ <p class="help-block help-block-error">验证码不正确。</p>
contains text 'Name cannot be blank'

Scenario Steps:

 4. $I->see("Name cannot be blank",".help-block") at tests\_support\FunctionalTester.php:26
 3. $I->see("Contact","h1") at tests\functional\ContactCest.php:23
 2. $I->submitForm("#contact-form",[]) at tests\functional\ContactCest.php:22
 1. $I->amOnuser(["site/contact"]) at tests\functional\ContactCest.php:12


---------
2) ContactCest: Check contact submit not correct email
 Test  tests\functional\ContactCest.php:checkContactSubmitNotCorrectEmail
 Step  See "Email is not a valid email address.",".help-block"
 Fail  Failed asserting that any element by '.help-block' on user /index-test.php/site/contact
+ <p class="help-block help-block-error"></p>
+ <p class="help-block help-block-error">Email不是有效的邮箱地址。</p>
+ <p class="help-block help-block-error"></p>
+ <p class="help-block help-block-error"></p>
+ <p class="help-block help-block-error"></p>
contains text 'Email is not a valid email address.'

Scenario Steps:

 3. $I->see("Email is not a valid email address.",".help-block") at tests\_support\FunctionalTester.php:26
 2. $I->submitForm("#contact-form",{"ContactForm[name]":"tester","ContactForm[email]":"tester.email","ContactForm[subject]":"test subject","ContactForm[b...})
 at tests\functional\ContactCest.php:34
 1. $I->amOnuser(["site/contact"]) at tests\functional\ContactCest.php:12


---------
3) LoginCest: Check empty
 Test  tests\functional\LoginCest.php:checkEmpty
 Step  See "Username cannot be blank.",".help-block"
 Fail  Failed asserting that any element by '.help-block' on user /index-test.php/site/login
+ <p class="help-block help-block-error">Username不能为空。</p>
+ <p class="help-block help-block-error">Password不能为空。</p>
+ <p class="help-block help-block-error"></p>
contains text 'Username cannot be blank.'

Scenario Steps:

 3. $I->see("Username cannot be blank.",".help-block") at tests\_support\FunctionalTester.php:26
 2. $I->submitForm("#login-form",{"LoginForm[username]":"","LoginForm[password]":""}) at tests\functional\LoginCest.php:42
 1. $I->amOnRoute("site/login") at tests\functional\LoginCest.php:29


---------
4) SignupCest: Signup with empty fields
 Test  tests\functional\SignupCest.php:signupWithEmptyFields
 Step  See "Username cannot be blank.",".help-block"
 Fail  Failed asserting that any element by '.help-block' on user /index-test.php/site/signup
+ <p class="help-block help-block-error">Username不能为空。</p>
+ <p class="help-block help-block-error">Email不能为空。</p>
+ <p class="help-block help-block-error">Password不能为空。</p>
contains text 'Username cannot be blank.'

Scenario Steps:

 5. $I->see("Username cannot be blank.",".help-block") at tests\_support\FunctionalTester.php:26
 4. $I->submitForm("#form-signup",[]) at tests\functional\SignupCest.php:21
 3. $I->see("Please fill out the following fields to signup:") at tests\functional\SignupCest.php:20
 2. $I->see("Signup","h1") at tests\functional\SignupCest.php:19
 1. $I->amOnRoute("site/signup") at tests\functional\SignupCest.php:14


---------
5) SignupCest: Signup with wrong email
 Test  tests\functional\SignupCest.php:signupWithWrongEmail
 Step  See "Email is not a valid email address.",".help-block"
 Fail  Failed asserting that any element by '.help-block' on user /index-test.php/site/signup
+ <p class="help-block help-block-error"></p>
+ <p class="help-block help-block-error">Email不是有效的邮箱地址。</p>
+ <p class="help-block help-block-error"></p>
contains text 'Email is not a valid email address.'

Scenario Steps:

 5. $I->see("Email is not a valid email address.",".help-block") at tests\functional\SignupCest.php:39
 4. $I->dontSee("Password cannot be blank.",".help-block") at tests\functional\SignupCest.php:38
 3. $I->dontSee("Username cannot be blank.",".help-block") at tests\functional\SignupCest.php:37
 2. $I->submitForm("#form-signup",{"SignupForm[username]":"tester","SignupForm[email]":"ttttt","SignupForm[password]":"tester_password"}) at tests\functional\
SignupCest.php:32
 1. $I->amOnRoute("site/signup") at tests\functional\SignupCest.php:14


FAILURES!
Tests: 20, Assertions: 48, Failures: 5.
vendor/bin/codecept run

4、(三) 的第22步骤需要还原,此步骤导致样例测试报错,调整为仅web应用情况下,才支持内容协商功能

(1)还原 \common\config\main.php 在第22步骤所做的编辑
(2)编辑 \backend\config\main.php、\frontend\config\main.php

注:如果请求中没有检测到语言, 使用 [[languages]] 第一个配置项。

    'bootstrap' => ['log', 'contentNegotiator'],
    'components' => [

        'contentNegotiator' => [
            'class' => 'yii\filters\ContentNegotiator',
            'languages' => [
                'en-US',
                'zh-CN',
            ],
        ],

    ],

(3)编辑 \api\config\main.php

    'bootstrap' => ['log', 'contentNegotiator'],
    'components' => [

        'contentNegotiator' => [
            'class' => 'yii\filters\ContentNegotiator',
            'formats' => [
                'application/json' => yii\web\Response::FORMAT_JSON,
                'application/xml' => yii\web\Response::FORMAT_XML,
            ],
            'languages' => [
                'en-US',
                'zh-CN',
            ],
        ],

    ],

删除

        'response' => [
            'format' => yii\web\Response::FORMAT_JSON,
        ],

5、打开 Windows PowerShell,执行 init 命令并选择 dev 作为环境,然后重新配置数据库连接

.\init
0
yes
Yes
Yes
Yes
Yes
Yes

6、运行所有的样例测试,符合预期,如图2

图2

vendor/bin/codecept run

7、要开始编写 API 测试,创建 API 测试套件,运行命令,如图3

图3

vendor/bin/codecept generate:suite api -c api
Helper \api\tests\Helper\Api was created in E:\wwwroot\github-shuijingwan-yii2-app-advanced\api\tests/_support\Helper\Api.php
Actor ApiTester was created in E:\wwwroot\github-shuijingwan-yii2-app-advanced\api\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

8、编辑 \api\tests\api.suite.yml 以启用该套件的模块

actor: ApiTester
modules:
    enabled:
        - REST:
            url: /v1
            depends: Yii2
        - \api\tests\Helper\Api
    config:
        - Yii2

9、创建测试:获取用户列表(user/IndexEmpty),如图4

图4

vendor/bin/codecept generate:cest api user/IndexEmpty -c api
Test was created in E:\wwwroot\github-shuijingwan-yii2-app-advanced\api\tests\api\user\IndexEmptyCest.php

10、[[yii\base\Application::version|version]],该属性指定应用的版本,默认为’1.0’, 配置为:1.0.0,编辑 \api\config\main.php,此值应与 Git 上的 tag 保持一致

    'version' => '1.0.0',

11、编辑 \api\tests\_support\Helper\Api.php,添加 获取当前版本号(次版本号.修订号) 的方法

<?php
namespace api\tests\Helper;

use Yii;
use yii\helpers\StringHelper;

// here you can define custom actions
// all public methods declared in helper class will be available in $I

class Api extends \Codeception\Module
{

    // 获取当前版本号(次版本号.修订号)
    public function getMinorPatch() {
        $version = StringHelper::explode(Yii::$app->version, '.');
        return $version[1] . '.' . $version[2];
    }

}

12、运行 build 命令,新的功能已添加到 ApiTester 类,如图5

图5

vendor/bin/codecept build -- -c api

13、\api\tests\_support\_generated\ApiTesterActions.php 成功生成,新增方法 getMinorPatch(),查看,如图6

图6

14、安装 flow/jsonpath ,以检查响应的结构,如图7

图7

composer require --prefer-dist flow/jsonpath
Using version ^0.4.0 for flow/jsonpath
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing flow/jsonpath (0.4.0): Downloading (100%)
Writing lock file
Generating autoload files

15、需要为每个测试实现一个公共方法,让 indexIsJson 通过 REST API 测试获取用户列表(用户列表为空、JSON响应),让 indexIsXml 通过 REST API 测试获取用户列表(用户列表为空、XML响应),编辑 \api\tests\api\user\IndexEmptyCest.php

<?php
namespace api\tests\user;

use Yii;
use api\tests\ApiTester;
use Codeception\Util\HttpCode;
use Codeception\Util\Xml;

class IndexEmptyCest
{
    public function _before(ApiTester $I)
    {
    }

    public function _after(ApiTester $I)
    {
    }

    // 获取用户列表(用户列表为空、JSON响应)
    public function indexIsJson(ApiTester $I)
    {
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users');
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson([
            'code' => 20001,
            'message' => Yii::t('error', '20001'),
        ]);
    }

    // 获取用户列表(用户列表为空、XML响应)
    public function indexIsXml(ApiTester $I)
    {
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users');
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20001]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => Yii::t('error', '20001')]));
    }
}

16、运行测试,获取详细的输出,可看到一步一步的行为报告,符合预期,如图8

图8

vendor/bin/codecept run --steps -- -c api
Codeception PHP Testing Framework v2.4.1
Powered by PHPUnit 7.1.2 by Sebastian Bergmann and contributors.

Api\tests.api Tests (2) -----------------------------------------------------
IndexEmptyCest: Index is json
Signature: api\tests\user\IndexEmptyCest:indexIsJson
Test: tests\api\user\IndexEmptyCest.php:indexIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send get "/users"
 I see response code is 200
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20001,"message":"User list is empty"}
 PASSED

IndexEmptyCest: Index is xml
Signature: api\tests\user\IndexEmptyCest:indexIsXml
Test: tests\api\user\IndexEmptyCest.php:indexIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send get "/users"
 I see response code is 200
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

-----------------------------------------------------------------------------

Api\tests.functional Tests (0) ----------------------------------------------
-----------------------------------------------------------------------------

Api\tests.unit Tests (0) ----------------------------------------------------
-----------------------------------------------------------------------------


Time: 1.11 seconds, Memory: 16.00MB

OK (2 tests, 12 assertions)

17、创建测试:获取用户列表(user/Index)

vendor/bin/codecept generate:cest api user/Index -c api

18、需要为每个测试实现一个公共方法,使用 Fixtures,让 indexIsJson 通过 REST API 测试获取用户列表(获取用户列表成功、JSON响应),让 indexIsXml 通过 REST API 测试获取用户列表(获取用户列表成功、XML响应),编辑 \api\tests\api\user\IndexCest.php

<?php
namespace api\tests\user;

use Yii;
use api\tests\ApiTester;
use Codeception\Util\HttpCode;
use Codeception\Util\Xml;
use api\fixtures\UserFixture;

class IndexCest
{
    public function _before(ApiTester $I)
    {
    }

    public function _after(ApiTester $I)
    {
    }

    /**
     * @return array
     */    public function _fixtures()
    {
        return [
            'user' => [
                'class' => UserFixture::className(),
                'dataFile' => codecept_data_dir() . 'user.php'
            ]
        ];
    }

    // 获取用户列表(获取用户列表成功、JSON响应)
    public function indexIsJson(ApiTester $I)
    {
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users');
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        $I->seeResponseJsonMatchesJsonPath('$.data');
        $I->seeResponseJsonMatchesJsonPath('$.data.items');
        $I->seeResponseJsonMatchesJsonPath('$.data._links');
        $I->seeResponseJsonMatchesJsonPath('$.data._meta');
        // 检查响应的数据
        $I->seeResponseContainsJson([
            'code' => 10000,
            'message' => Yii::t('success', '10001'),
        ]);
    }

    // 获取用户列表(获取用户列表成功、XML响应)
    public function indexIsXml(ApiTester $I)
    {
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users');
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        $I->seeXmlResponseMatchesXpath('//data');
        $I->seeXmlResponseMatchesXpath('//data/items');
        $I->seeXmlResponseMatchesXpath('//data/_links');
        $I->seeXmlResponseMatchesXpath('//data/_meta');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 10000]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => Yii::t('success', '10001')]));
    }
}

19、运行测试,获取详细的输出,可看到一步一步的行为报告,符合预期

vendor/bin/codecept run --steps -- -c api
IndexCest: Index is json
Signature: api\tests\user\IndexCest:indexIsJson
Test: tests\api\user\IndexCest.php:indexIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send get "/users"
 I see response code is 200
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response json matches json path "$.data"
 I see response json matches json path "$.data.items"
 I see response json matches json path "$.data._links"
 I see response json matches json path "$.data._meta"
 I see response contains json {"code":10000,"message":"Get user list success"}
 PASSED

IndexCest: Index is xml
Signature: api\tests\user\IndexCest:indexIsXml
Test: tests\api\user\IndexCest.php:indexIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send get "/users"
 I see response code is 200
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response matches xpath "//data"
 I see xml response matches xpath "//data/items"
 I see xml response matches xpath "//data/_links"
 I see xml response matches xpath "//data/_meta"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

20、GET /users/{id}: 返回用户 {id} 的详细信息,创建测试:获取用户详情

vendor/bin/codecept generate:cest api user/View -c api

21、编辑 \api\tests\api\user\ViewCest.php,获取用户详情(用户ID:{id},不存在/用户ID:{id},的状态为已删除/获取用户详情成功)

<?php
namespace api\tests\user;

use Yii;
use api\tests\ApiTester;
use Codeception\Util\HttpCode;
use Codeception\Util\Xml;
use api\fixtures\UserFixture;

class ViewCest
{
    const STATUS_DELETED = 0; //状态:已删除
    const STATUS_ACTIVE = 10; //状态:活跃

    public function _before(ApiTester $I)
    {
    }

    public function _after(ApiTester $I)
    {
    }

    /**
     * @return array
     */    public function _fixtures()
    {
        return [
            'user' => [
                'class' => UserFixture::className(),
                'dataFile' => codecept_data_dir() . 'user.php'
            ]
        ];
    }

    // 获取用户详情(获取用户详情成功、JSON响应)
    public function viewIsJson(ApiTester $I)
    {
        $id = 1;
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        $I->seeResponseJsonMatchesJsonPath('$.data');
        // 检查响应的数据
        $I->seeResponseContainsJson([
            'code' => 10000,
            'message' => Yii::t('success', '10002'),
        ]);
    }

    // 获取用户详情(获取用户详情成功、XML响应)
    public function viewIsXml(ApiTester $I)
    {
        $id = 1;
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        $I->seeXmlResponseMatchesXpath('//data');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 10000]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => Yii::t('success', '10002')]));
    }

    // 获取用户详情,使用不存在的ID(用户ID:{id},不存在、JSON响应)
    public function viewWithNotExistIdIsJson(ApiTester $I)
    {
        $id = 9999;
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::NOT_FOUND); // 404
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson([
            'code' => 20002,
            'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20002'), ['id' => $id])),
        ]);
    }

    // 获取用户详情,使用不存在的ID(用户ID:{id},不存在、XML响应)
    public function viewWithNotExistIdIsXml(ApiTester $I)
    {
        $id = 9999;
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::NOT_FOUND); // 404
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20002]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => Yii::t('error', Yii::t('error', Yii::t('error', '20002'), ['id' => $id]))]));
    }

    // 获取用户详情(用户ID:{id},的状态为已删除、JSON响应)
    public function viewStatusDeletedIsJson(ApiTester $I)
    {
        $id = 2;
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson([
            'code' => 20003,
            'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20003'), ['id' => $id])),
        ]);
    }

    // 获取用户详情(用户ID:{id},的状态为已删除、XML响应)
    public function viewStatusDeletedIsXml(ApiTester $I)
    {
        $id = 2;
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendGET('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20003]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => Yii::t('error', Yii::t('error', Yii::t('error', '20003'), ['id' => $id]))]));
    }
}

22、运行测试,获取详细的输出,可看到一步一步的行为报告,符合预期

vendor/bin/codecept run --steps -- -c api
ViewCest: View is json
Signature: api\tests\user\ViewCest:viewIsJson
Test: tests\api\user\ViewCest.php:viewIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send get "/users/1"
 I see response code is 200
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response json matches json path "$.data"
 I see response contains json {"code":10000,"message":"Get user details success"}
 PASSED

ViewCest: View is xml
Signature: api\tests\user\ViewCest:viewIsXml
Test: tests\api\user\ViewCest.php:viewIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send get "/users/1"
 I see response code is 200
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response matches xpath "//data"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

ViewCest: View with not exist id is json
Signature: api\tests\user\ViewCest:viewWithNotExistIdIsJson
Test: tests\api\user\ViewCest.php:viewWithNotExistIdIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send get "/users/9999"
 I see response code is 404
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20002,"message":"User ID: 9999, does not exist"}
 PASSED

ViewCest: View with not exist id is xml
Signature: api\tests\user\ViewCest:viewWithNotExistIdIsXml
Test: tests\api\user\ViewCest.php:viewWithNotExistIdIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send get "/users/9999"
 I see response code is 404
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

ViewCest: View status deleted is json
Signature: api\tests\user\ViewCest:viewStatusDeletedIsJson
Test: tests\api\user\ViewCest.php:viewStatusDeletedIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send get "/users/2"
 I see response code is 200
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20003,"message":"User ID: 2, status is deleted"}
 PASSED

ViewCest: View status deleted is xml
Signature: api\tests\user\ViewCest:viewStatusDeletedIsXml
Test: tests\api\user\ViewCest.php:viewStatusDeletedIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send get "/users/2"
 I see response code is 200
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

23、POST /users: 创建一个新用户,创建测试:创建用户

vendor/bin/codecept generate:cest api user/Create -c api

24、编辑 \api\tests\api\user\CreateCest.php,支持(创建用户成功、数据验证失败:{firstErrors})

<?php
namespace api\tests\user;

use Yii;
use api\tests\ApiTester;
use Codeception\Util\HttpCode;
use Codeception\Util\Xml;
use api\fixtures\UserFixture;

class CreateCest
{
    public function _before(ApiTester $I)
    {
    }

    public function _after(ApiTester $I)
    {
    }

    /**
     * @return array
     */    public function _fixtures()
    {
        return [
            'user' => [
                'class' => UserFixture::className(),
                'dataFile' => codecept_data_dir() . 'user.php'
            ]
        ];
    }

    // 创建用户(创建用户成功、JSON响应)
    public function createIsJson(ApiTester $I)
    {
        $data = [
            'username' => '111111',
            'email' => '111111@163.com',
            'password' => '111111',
        ];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPOST('/users', $data);
        $I->seeResponseCodeIs(HttpCode::CREATED); // 201
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        $I->seeResponseJsonMatchesJsonPath('$.data');
        $I->seeResponseJsonMatchesJsonPath('$.data.username');
        $I->seeResponseJsonMatchesJsonPath('$.data.email');
        // 检查响应的数据
        $I->seeResponseContainsJson([
            'code' => 10000,
            'message' => Yii::t('success', '10003'),
            'data' => [
                'username' => $data['username'],
                'email' => $data['email'],
            ],
        ]);
    }

    // 创建用户(创建用户成功、XML响应)
    public function createIsXml(ApiTester $I)
    {
        $data = [
            'username' => '111111',
            'email' => '111111@163.com',
            'password' => '111111',
        ];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPOST('/users', $data);
        $I->seeResponseCodeIs(HttpCode::CREATED); // 201
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        $I->seeXmlResponseMatchesXpath('//data');
        $I->seeXmlResponseMatchesXpath('//data//username');
        $I->seeXmlResponseMatchesXpath('//data//email');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 10000]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => Yii::t('success', '10003')]));
        $I->seeXmlResponseIncludes(Xml::toXml(['username' => $data['username']]));
        $I->seeXmlResponseIncludes(Xml::toXml(['email' => $data['email']]));
    }

    // 创建用户,使用空的字段值(数据验证失败:{firstErrors}、JSON响应)
    public function createWithEmptyFieldsIsJson(ApiTester $I)
    {
        $data = [];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPOST('/users', $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson(['code' => 20004]);
        $I->seeResponseContainsJson(['message' => 'Data validation failed: Username cannot be blank.']);
    }

    // 创建用户,使用空的字段值(数据验证失败:{firstErrors}、XML响应)
    public function createWithEmptyFieldsIsXml(ApiTester $I)
    {
        $data = [];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPOST('/users', $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20004]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => 'Data validation failed: Username cannot be blank.']));
    }

    // 创建用户,使用错误的邮箱(数据验证失败:{firstErrors}、JSON响应)
    public function createWithWrongEmailIsJson(ApiTester $I)
    {
        $data = [
            'username' => '111111',
            'email' => '111111',
            'password' => '111111',
        ];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPOST('/users', $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson(['code' => 20004]);
        $I->seeResponseContainsJson(['message' => 'Data validation failed: Email is not a valid email address.']);
    }

    // 创建用户,使用错误的邮箱(数据验证失败:{firstErrors}、XML响应)
    public function createWithWrongEmailIsXml(ApiTester $I)
    {
        $data = [
            'username' => '111111',
            'email' => '111111',
            'password' => '111111',
        ];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPOST('/users', $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20004]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => 'Data validation failed: Email is not a valid email address.']));
    }

    // 创建用户,使用已经存在的字段值(数据验证失败:{firstErrors}、JSON响应)
    public function createWithExistFieldsIsJson(ApiTester $I)
    {
        $data = [
            'username' => 'troy.becker',
            'email' => 'nicolas.dianna@hotmail.com',
            'password' => 'some_password',
        ];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPOST('/users', $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson(['code' => 20004]);
        $I->seeResponseContainsJson(['message' => 'Data validation failed: Username "troy.becker" has already been taken.']);
    }

    // 创建用户,使用已经存在的字段值(数据验证失败:{firstErrors}、XML响应)
    public function createWithExistFieldsIsXml(ApiTester $I)
    {
        $data = [
            'username' => 'troy.becker',
            'email' => 'nicolas.dianna@hotmail.com',
            'password' => 'some_password',
        ];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPOST('/users', $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20004]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => 'Data validation failed: Username "troy.becker" has already been taken.']));
    }
}

25、运行测试,获取详细的输出,可看到一步一步的行为报告,符合预期

vendor/bin/codecept run --steps -- -c api
CreateCest: Create is json
Signature: api\tests\user\CreateCest:createIsJson
Test: tests\api\user\CreateCest.php:createIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send post "/users",{"username":"111111","email":"111111@163.com","password":"111111"}
 I see response code is 201
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response json matches json path "$.data"
 I see response json matches json path "$.data.username"
 I see response json matches json path "$.data.email"
 I see response contains json {"code":10000,"message":"Create user success","data":{"username":"111111","email":"111...}
 PASSED

CreateCest: Create is xml
Signature: api\tests\user\CreateCest:createIsXml
Test: tests\api\user\CreateCest.php:createIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send post "/users",{"username":"111111","email":"111111@163.com","password":"111111"}
 I see response code is 201
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response matches xpath "//data"
 I see xml response matches xpath "//data//username"
 I see xml response matches xpath "//data//email"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

CreateCest: Create with empty fields is json
Signature: api\tests\user\CreateCest:createWithEmptyFieldsIsJson
Test: tests\api\user\CreateCest.php:createWithEmptyFieldsIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send post "/users",[]
 I see response code is 422
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20004}
 I see response contains json {"message":"Data validation failed: Username cannot be blank."}
 PASSED

CreateCest: Create with empty fields is xml
Signature: api\tests\user\CreateCest:createWithEmptyFieldsIsXml
Test: tests\api\user\CreateCest.php:createWithEmptyFieldsIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send post "/users",[]
 I see response code is 422
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

CreateCest: Create with wrong email is json
Signature: api\tests\user\CreateCest:createWithWrongEmailIsJson
Test: tests\api\user\CreateCest.php:createWithWrongEmailIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send post "/users",{"username":"111111","email":"111111","password":"111111"}
 I see response code is 422
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20004}
 I see response contains json {"message":"Data validation failed: Email is not a valid email address."}
 PASSED

CreateCest: Create with wrong email is xml
Signature: api\tests\user\CreateCest:createWithWrongEmailIsXml
Test: tests\api\user\CreateCest.php:createWithWrongEmailIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send post "/users",{"username":"111111","email":"111111","password":"111111"}
 I see response code is 422
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

CreateCest: Create with exist fields is json
Signature: api\tests\user\CreateCest:createWithExistFieldsIsJson
Test: tests\api\user\CreateCest.php:createWithExistFieldsIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send post "/users",{"username":"troy.becker","email":"nicolas.dianna@hotmail.com","password":"some_password"}
 I see response code is 422
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20004}
 I see response contains json {"message":"Data validation failed: Username "troy.becker" has already been taken."}
 PASSED

CreateCest: Create with exist fields is xml
Signature: api\tests\user\CreateCest:createWithExistFieldsIsXml
Test: tests\api\user\CreateCest.php:createWithExistFieldsIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send post "/users",{"username":"troy.becker","email":"nicolas.dianna@hotmail.com","password":"some_password"}
 I see response code is 422
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

26、PUT /users/{id}: 更新一个用户,复制 \api\tests\api\user\CreateCest.php 为 \api\tests\api\user\UpdateCest.php,更新用户(用户ID:{id},不存在/数据验证失败:{firstErrors}/更新用户成功),编辑

<?php
namespace api\tests\user;

use Yii;
use api\tests\ApiTester;
use Codeception\Util\HttpCode;
use Codeception\Util\Xml;
use api\fixtures\UserFixture;

class UpdateCest
{
    public function _before(ApiTester $I)
    {
    }

    public function _after(ApiTester $I)
    {
    }

    /**
     * @return array
     */    public function _fixtures()
    {
        return [
            'user' => [
                'class' => UserFixture::className(),
                'dataFile' => codecept_data_dir() . 'user.php'
            ]
        ];
    }

    // 更新用户(更新用户成功、JSON响应)
    public function updateIsJson(ApiTester $I)
    {
        $id = 1;
        $data = [
            'email' => '111111@163.com',
            'password' => '111111',
            'status' => 0,
        ];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        $I->seeResponseJsonMatchesJsonPath('$.data');
        $I->seeResponseJsonMatchesJsonPath('$.data.email');
        $I->seeResponseJsonMatchesJsonPath('$.data.status');
        // 检查响应的数据
        $I->seeResponseContainsJson([
            'code' => 10000,
            'message' => Yii::t('success', '10004'),
            'data' => [
                'email' => $data['email'],
                'status' => $data['status'],
            ],
        ]);
    }

    // 更新用户(更新用户成功、XML响应)
    public function updateIsXml(ApiTester $I)
    {
        $id = 1;
        $data = [
            'email' => '111111@163.com',
            'password' => '111111',
            'status' => 0,
        ];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        $I->seeXmlResponseMatchesXpath('//data');
        $I->seeXmlResponseMatchesXpath('//data//email');
        $I->seeXmlResponseMatchesXpath('//data//status');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 10000]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => Yii::t('success', '10004')]));
        $I->seeXmlResponseIncludes(Xml::toXml(['email' => $data['email']]));
        $I->seeXmlResponseIncludes(Xml::toXml(['status' => $data['status']]));
    }

    // 更新用户,使用不存在的ID(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithNotExistIdIsJson(ApiTester $I)
    {
        $id = 9999;
        $data = [
            'email' => '111111@163.com',
            'password' => '111111',
            'status' => 0,
        ];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::NOT_FOUND); // 404
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson(['code' => 20002]);
        $I->seeResponseContainsJson(['message' => 'User ID: 9999, does not exist']);
    }

    // 更新用户,使用不存在的ID(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithNotExistIdIsXml(ApiTester $I)
    {
        $id = 9999;
        $data = [
            'email' => '111111@163.com',
            'password' => '111111',
            'status' => 0,
        ];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::NOT_FOUND); // 404
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20002]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => 'User ID: 9999, does not exist']));
    }

    // 更新用户,使用空的字段值(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithEmptyFieldsIsJson(ApiTester $I)
    {
        $id = 1;
        $data = [];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson(['code' => 20004]);
        $I->seeResponseContainsJson(['message' => 'Data validation failed: Email cannot be blank.']);
    }

    // 更新用户,使用空的字段值(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithEmptyFieldsIsXml(ApiTester $I)
    {
        $id = 1;
        $data = [];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20004]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => 'Data validation failed: Email cannot be blank.']));
    }

    // 更新用户,使用错误的邮箱(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithWrongEmailIsJson(ApiTester $I)
    {
        $id = 1;
        $data = [
            'email' => '111111',
            'password' => '111111',
            'status' => 0,
        ];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson(['code' => 20004]);
        $I->seeResponseContainsJson(['message' => 'Data validation failed: Email is not a valid email address.']);
    }

    // 更新用户,使用错误的邮箱(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithWrongEmailIsXml(ApiTester $I)
    {
        $id = 1;
        $data = [
            'email' => '111111',
            'password' => '111111',
            'status' => 0,
        ];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20004]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => 'Data validation failed: Email is not a valid email address.']));
    }

    // 更新用户,使用已经存在的字段值(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithExistFieldsIsJson(ApiTester $I)
    {
        $id = 1;
        $data = [
            'email' => 'nicolas.dianna@hotmail.com',
            'password' => '111111',
            'status' => 0,
        ];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson(['code' => 20004]);
        $I->seeResponseContainsJson(['message' => 'Data validation failed: Email "nicolas.dianna@hotmail.com" has already been taken.']);
    }

    // 更新用户,使用已经存在的字段值(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithExistFieldsIsXml(ApiTester $I)
    {
        $id = 1;
        $data = [
            'email' => 'nicolas.dianna@hotmail.com',
            'password' => '111111',
            'status' => 0,
        ];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20004]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => 'Data validation failed: Email "nicolas.dianna@hotmail.com" has already been taken.']));
    }

    // 更新用户,使用超出范围的状态值(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithNotRangeStatusIsJson(ApiTester $I)
    {
        $id = 1;
        $data = [
            'email' => '111111@163.com',
            'password' => '111111',
            'status' => 5,
        ];
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson(['code' => 20004]);
        $I->seeResponseContainsJson(['message' => 'Data validation failed: Status is invalid.']);
    }

    // 更新用户,使用超出范围的状态值(数据验证失败:{firstErrors}、JSON响应)
    public function updateWithNotRangeStatusIsXml(ApiTester $I)
    {
        $id = 1;
        $data = [
            'email' => '111111@163.com',
            'password' => '111111',
            'status' => 5,
        ];
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendPUT('/users/' . $id, $data);
        $I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY); // 422
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20004]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => 'Data validation failed: Status is invalid.']));
    }

}

27、运行测试,获取详细的输出,可看到一步一步的行为报告,符合预期

vendor/bin/codecept run --steps -- -c api
UpdateCest: Update is json
Signature: api\tests\user\UpdateCest:updateIsJson
Test: tests\api\user\UpdateCest.php:updateIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send put "/users/1",{"email":"111111@163.com","password":"111111","status":0}
 I see response code is 200
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response json matches json path "$.data"
 I see response json matches json path "$.data.email"
 I see response json matches json path "$.data.status"
 I see response contains json {"code":10000,"message":"Update user success","data":{"email":"111111@163.com","status...}
 PASSED

UpdateCest: Update is xml
Signature: api\tests\user\UpdateCest:updateIsXml
Test: tests\api\user\UpdateCest.php:updateIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send put "/users/1",{"email":"111111@163.com","password":"111111","status":0}
 I see response code is 200
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response matches xpath "//data"
 I see xml response matches xpath "//data//email"
 I see xml response matches xpath "//data//status"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

UpdateCest: Update with not exist id is json
Signature: api\tests\user\UpdateCest:updateWithNotExistIdIsJson
Test: tests\api\user\UpdateCest.php:updateWithNotExistIdIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send put "/users/9999",{"email":"111111@163.com","password":"111111","status":0}
 I see response code is 404
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20002}
 I see response contains json {"message":"User ID: 9999, does not exist"}
 PASSED

UpdateCest: Update with not exist id is xml
Signature: api\tests\user\UpdateCest:updateWithNotExistIdIsXml
Test: tests\api\user\UpdateCest.php:updateWithNotExistIdIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send put "/users/9999",{"email":"111111@163.com","password":"111111","status":0}
 I see response code is 404
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

UpdateCest: Update with empty fields is json
Signature: api\tests\user\UpdateCest:updateWithEmptyFieldsIsJson
Test: tests\api\user\UpdateCest.php:updateWithEmptyFieldsIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send put "/users/1",[]
 I see response code is 422
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20004}
 I see response contains json {"message":"Data validation failed: Email cannot be blank."}
 PASSED

UpdateCest: Update with empty fields is xml
Signature: api\tests\user\UpdateCest:updateWithEmptyFieldsIsXml
Test: tests\api\user\UpdateCest.php:updateWithEmptyFieldsIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send put "/users/1",[]
 I see response code is 422
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

UpdateCest: Update with wrong email is json
Signature: api\tests\user\UpdateCest:updateWithWrongEmailIsJson
Test: tests\api\user\UpdateCest.php:updateWithWrongEmailIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send put "/users/1",{"email":"111111","password":"111111","status":0}
 I see response code is 422
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20004}
 I see response contains json {"message":"Data validation failed: Email is not a valid email address."}
 PASSED

UpdateCest: Update with wrong email is xml
Signature: api\tests\user\UpdateCest:updateWithWrongEmailIsXml
Test: tests\api\user\UpdateCest.php:updateWithWrongEmailIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send put "/users/1",{"email":"111111","password":"111111","status":0}
 I see response code is 422
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

UpdateCest: Update with exist fields is json
Signature: api\tests\user\UpdateCest:updateWithExistFieldsIsJson
Test: tests\api\user\UpdateCest.php:updateWithExistFieldsIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send put "/users/1",{"email":"nicolas.dianna@hotmail.com","password":"111111","status":0}
 I see response code is 422
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20004}
 I see response contains json {"message":"Data validation failed: Email "nicolas.dianna@hotmail.com" has already bee...}
 PASSED

UpdateCest: Update with exist fields is xml
Signature: api\tests\user\UpdateCest:updateWithExistFieldsIsXml
Test: tests\api\user\UpdateCest.php:updateWithExistFieldsIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send put "/users/1",{"email":"nicolas.dianna@hotmail.com","password":"111111","status":0}
 I see response code is 422
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

UpdateCest: Update with not range status is json
Signature: api\tests\user\UpdateCest:updateWithNotRangeStatusIsJson
Test: tests\api\user\UpdateCest.php:updateWithNotRangeStatusIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send put "/users/1",{"email":"111111@163.com","password":"111111","status":5}
 I see response code is 422
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20004}
 I see response contains json {"message":"Data validation failed: Status is invalid."}
 PASSED

UpdateCest: Update with not range status is xml
Signature: api\tests\user\UpdateCest:updateWithNotRangeStatusIsXml
Test: tests\api\user\UpdateCest.php:updateWithNotRangeStatusIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send put "/users/1",{"email":"111111@163.com","password":"111111","status":5}
 I see response code is 422
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

28、DELETE /users/{id}: 删除用户,复制 \api\tests\api\user\ViewCest.php 为 \api\tests\api\user\DeleteCest.php,删除用户(用户ID:{id},不存在/删除用户成功),编辑

<?php
namespace api\tests\user;

use Yii;
use api\tests\ApiTester;
use Codeception\Util\HttpCode;
use Codeception\Util\Xml;
use api\fixtures\UserFixture;

class DeleteCest
{
    public function _before(ApiTester $I)
    {
    }

    public function _after(ApiTester $I)
    {
    }

    /**
     * @return array
     */    public function _fixtures()
    {
        return [
            'user' => [
                'class' => UserFixture::className(),
                'dataFile' => codecept_data_dir() . 'user.php'
            ]
        ];
    }

    // 删除用户(删除用户成功、JSON响应)
    public function deleteIsJson(ApiTester $I)
    {
        $id = 1;
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendDELETE('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson([
            'code' => 10000,
            'message' => Yii::t('success', '10005'),
        ]);
    }

    // 删除用户(删除用户成功、XML响应)
    public function deleteIsXml(ApiTester $I)
    {
        $id = 1;
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendDELETE('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 10000]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => Yii::t('success', '10005')]));
    }

    // 获取用户详情,使用不存在的ID(用户ID:{id},不存在、JSON响应)
    public function deleteWithNotExistIdIsJson(ApiTester $I)
    {
        $id = 9999;
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendDELETE('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::NOT_FOUND); // 404
        $I->seeResponseIsJson();
        // 检查响应的结构
        $I->seeResponseJsonMatchesJsonPath('$.code');
        $I->seeResponseJsonMatchesJsonPath('$.message');
        // 检查响应的数据
        $I->seeResponseContainsJson([
            'code' => 20002,
            'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20002'), ['id' => $id])),
        ]);
    }

    // 获取用户详情,使用不存在的ID(用户ID:{id},不存在、XML响应)
    public function deleteWithNotExistIdIsXml(ApiTester $I)
    {
        $id = 9999;
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendDELETE('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::NOT_FOUND); // 404
        $I->seeResponseIsXml();
        // 检查响应的结构
        $I->seeXmlResponseMatchesXpath('//code');
        $I->seeXmlResponseMatchesXpath('//message');
        // 检查响应的数据
        $I->seeXmlResponseIncludes(Xml::toXml(['code' => 20002]));
        $I->seeXmlResponseIncludes(Xml::toXml(['message' => Yii::t('error', Yii::t('error', Yii::t('error', '20002'), ['id' => $id]))]));
    }
}

29、运行测试,仅测试 api user/CreateCest.php,获取详细的输出,可看到一步一步的行为报告,符合预期,如图9

图9

vendor/bin/codecept run --steps -- -c api api user/DeleteCest.php
Codeception PHP Testing Framework v2.4.1
Powered by PHPUnit 7.1.2 by Sebastian Bergmann and contributors.

Api\tests.api Tests (4) ------------------------------------------------------------------------------------------------
DeleteCest: Delete is json
Signature: api\tests\user\DeleteCest:deleteIsJson
Test: tests\api\user\DeleteCest.php:deleteIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send delete "/users/1"
 I see response code is 200
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":10000,"message":"Delete user success"}
 PASSED

DeleteCest: Delete is xml
Signature: api\tests\user\DeleteCest:deleteIsXml
Test: tests\api\user\DeleteCest.php:deleteIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send delete "/users/1"
 I see response code is 200
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

DeleteCest: Delete with not exist id is json
Signature: api\tests\user\DeleteCest:deleteWithNotExistIdIsJson
Test: tests\api\user\DeleteCest.php:deleteWithNotExistIdIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send delete "/users/9999"
 I see response code is 404
 I see response is json
 I see response json matches json path "$.code"
 I see response json matches json path "$.message"
 I see response contains json {"code":20002,"message":"User ID: 9999, does not exist"}
 PASSED

DeleteCest: Delete with not exist id is xml
Signature: api\tests\user\DeleteCest:deleteWithNotExistIdIsXml
Test: tests\api\user\DeleteCest.php:deleteWithNotExistIdIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send delete "/users/9999"
 I see response code is 404
 I see response is xml
 I see xml response matches xpath "//code"
 I see xml response matches xpath "//message"
 I see xml response includes "DOMDocument"
 I see xml response includes "DOMDocument"
 PASSED

------------------------------------------------------------------------------------------------------------------------


Time: 1.25 seconds, Memory: 16.00MB

OK (4 tests, 24 assertions)

30、OPTIONS /users: 显示关于末端 /users 支持的动词;OPTIONS /users/{id}: 显示关于末端 /users/{id} 支持的动词,复制 \api\tests\api\user\IndexCest.php 为 \api\tests\api\user\OptionsCest.php,编辑

<?php
namespace api\tests\user;

use Yii;
use api\tests\ApiTester;
use Codeception\Util\HttpCode;
use Codeception\Util\Xml;
use api\fixtures\UserFixture;

class OptionsCest
{
    public function _before(ApiTester $I)
    {
    }

    public function _after(ApiTester $I)
    {
    }

    /**
     * @return array
     */    public function _fixtures()
    {
        return [
            'user' => [
                'class' => UserFixture::className(),
                'dataFile' => codecept_data_dir() . 'user.php'
            ]
        ];
    }

    // 显示关于末端 /users 支持的动词(JSON响应)
    public function optionsIsJson(ApiTester $I)
    {
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendOPTIONS('/users');
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        // 检查响应的数据
        $I->seeHttpHeader('Allow', 'GET, POST, HEAD, OPTIONS');
        $I->seeResponseEquals('');
    }

    // 显示关于末端 /users 支持的动词(XML响应)
    public function optionsIsXml(ApiTester $I)
    {
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendOPTIONS('/users');
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        // 检查响应的数据
        $I->seeHttpHeader('Allow', 'GET, POST, HEAD, OPTIONS');
        $I->seeResponseEquals('');
    }

    // 显示关于末端 /users/{id} 支持的动词(JSON响应)
    public function optionsIdIsJson(ApiTester $I)
    {
        $id = 1;
        $I->haveHttpHeader('Accept', 'application/json; version=' . $I->getMinorPatch() . '');
        $I->sendOPTIONS('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        // 检查响应的数据
        $I->seeHttpHeader('Allow', 'GET, PUT, PATCH, DELETE, HEAD, OPTIONS');
        $I->seeResponseEquals('');
    }

    // 显示关于末端 /users/{id} 支持的动词(XML响应)
    public function optionsIdIsXml(ApiTester $I)
    {
        $id = 1;
        $I->haveHttpHeader('Accept', 'application/xml; version=' . $I->getMinorPatch() . '');
        $I->sendOPTIONS('/users/' . $id);
        $I->seeResponseCodeIs(HttpCode::OK); // 200
        // 检查响应的数据
        $I->seeHttpHeader('Allow', 'GET, PUT, PATCH, DELETE, HEAD, OPTIONS');
        $I->seeResponseEquals('');
    }
}

31、运行测试,仅测试 api user/OptionsCest.php,获取详细的输出,可看到一步一步的行为报告,符合预期

vendor/bin/codecept run --steps -- -c api api user/OptionsCest.php
Codeception PHP Testing Framework v2.4.1
Powered by PHPUnit 7.1.2 by Sebastian Bergmann and contributors.

Api\tests.api Tests (4) ------------------------------------------------------------------------------------------------
OptionsCest: Options is json
Signature: api\tests\user\OptionsCest:optionsIsJson
Test: tests\api\user\OptionsCest.php:optionsIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send options "/users"
 I see response code is 200
 I see http header "Allow","GET, POST, HEAD, OPTIONS"
 I see response equals ""
 PASSED

OptionsCest: Options is xml
Signature: api\tests\user\OptionsCest:optionsIsXml
Test: tests\api\user\OptionsCest.php:optionsIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send options "/users"
 I see response code is 200
 I see http header "Allow","GET, POST, HEAD, OPTIONS"
 I see response equals ""
 PASSED

OptionsCest: Options id is json
Signature: api\tests\user\OptionsCest:optionsIdIsJson
Test: tests\api\user\OptionsCest.php:optionsIdIsJson
Scenario --
 I get minor patch
 I have http header "Accept","application/json; version=0.0"
 I send options "/users/1"
 I see response code is 200
 I see http header "Allow","GET, PUT, PATCH, DELETE, HEAD, OPTIONS"
 I see response equals ""
 PASSED

OptionsCest: Options id is xml
Signature: api\tests\user\OptionsCest:optionsIdIsXml
Test: tests\api\user\OptionsCest.php:optionsIdIsXml
Scenario --
 I get minor patch
 I have http header "Accept","application/xml; version=0.0"
 I send options "/users/1"
 I see response code is 200
 I see http header "Allow","GET, PUT, PATCH, DELETE, HEAD, OPTIONS"
 I see response equals ""
 PASSED

------------------------------------------------------------------------------------------------------------------------


Time: 1.19 seconds, Memory: 14.00MB

OK (4 tests, 12 assertions)

32、运行所有的样例测试,符合预期,如图10

图10

vendor/bin/codecept run
永夜