5

状況

コントローラーコード

<?php
App::uses('AppController', 'Controller');

class PostsController extends AppController {

    public function isAuthorized() {
        return true;
    }

    public function edit($id = null) {
        $this->autoRender = false;

        if (!$this->Post->exists($id)) {
            throw new NotFoundException(__('Invalid post'));
        }

        if ($this->Post->find('first', array(
            'conditions' => array(
                'Post.id' => $id,
                'Post.user_id' => $this->Auth->user('id')
            )
        ))) {
            echo 'Username: ' . $this->Auth->user('username') . '<br>';
            echo 'Created: ' . $this->Auth->user('created') . '<br>';
            echo 'Modified: ' . $this->Auth->user('modified') . '<br>';
            echo 'All:';
            pr($this->Auth->user());
            echo 'Modified: ' . $this->Auth->user('modified') . '<br>';
        } else {
            echo 'Unauthorized.';
        }
    }
}

ブラウザからの出力

Username: admin
Created: 2013-05-08 00:00:00
Modified: 2013-05-08 00:00:00
All:

Array
(
    [id] => 1
    [username] => admin
    [created] => 2013-05-08 00:00:00
    [modified] => 2013-05-08 00:00:00
)

Modified: 2013-05-08 00:00:00

テストコード

<?php
App::uses('PostsController', 'Controller');

class PostsControllerTest extends ControllerTestCase {

    public $fixtures = array(
        'app.post',
        'app.user'
    );

    public function testEdit() {
        $this->Controller = $this->generate('Posts', array(
            'components' => array(
                'Auth' => array('user')
            )
        ));

        $this->Controller->Auth->staticExpects($this->at(0))->method('user')->with('id')->will($this->returnValue(1));
        $this->Controller->Auth->staticExpects($this->at(1))->method('user')->with('username')->will($this->returnValue('admin'));
        $this->Controller->Auth->staticExpects($this->at(2))->method('user')->with('created')->will($this->returnValue('2013-05-08 00:00:00'));
        $this->Controller->Auth->staticExpects($this->at(3))->method('user')->with('modified')->will($this->returnValue('2013-05-08 00:00:00'));
        $this->Controller->Auth->staticExpects($this->at(4))->method('user')->will($this->returnValue(array(
            'id' => 1,
            'username' => 'admin',
            'created' => '2013-05-08 00:00:00',
            'modified' => '2013-05-08 00:00:00'
        )));

        $this->testAction('/posts/edit/1', array('method' => 'get'));
    }
}

テストからの出力

Username: admin
Created: 2013-05-08 00:00:00
Modified: 2013-05-08 00:00:00
All:

Array
(
    [id] => 1
    [username] => admin
    [created] => 2013-05-08 00:00:00
    [modified] => 2013-05-08 00:00:00
)

Modified: 

問題

ここには実際には 3 つの問題があります。

  1. テスト コードは非常に反復的です。
  2. テストからの出力の 2 番目の "Modified" 行は空白です。ブラウザからの出力のように「2013-05-08 00:00:00」になるはずです。
  3. コントローラーのコードを変更して、"Username" と "Created" のecho 'Email: ' . $this->Auth->user('email') . '<br>';間に (たとえば)という行を追加すると、テストは次のエラーで失敗します: . はもはや真ではないので、これは理にかなっています。echoExpectation failed for method name is equal to <string:user> when invoked at sequence index 2$this->at(1)

私の質問

(1) 繰り返しがなく、(2) テストがブラウザーと同じものを出力し、(3)$this->Auth->user('foo')テストを壊さずにどこにでもコードを追加できるように、Auth コンポーネントをモックするにはどうすればよいですか?

4

1 に答える 1

9

これに答える前に、CakePHP フレームワークを使用した経験がないことを認めなければなりません。しかし、私は Symfony フレームワークと組み合わせて PHPUnit を使用した経験がかなりあり、同様の問題に遭遇しました。ポイントに対処するには:

  1. ポイント3への私の答えを見てください

  2. この理由は、...->staticExpects($this->at(5))...Auth->user() への 6 回目の呼び出しをカバーする追加のステートメントが必要だからです。これらのステートメントは、指定された値を持つ Auth->user() への呼び出しに対して返される値を定義しません。たとえば、Auth オブジェクトへの 2 回目の呼び出しは、メソッド user() に対してパラメーター 'username' を指定する必要があると定義しています。この場合、'admin' が返されます。ただし、次のポイントのアプローチに従えば、これはもはや問題にはなりません。

  3. ここで達成しようとしているのは、コントローラーを Auth コンポーネントから独立してテストすることであると想定しています (率直に言って、コントローラーがユーザー オブジェクトに対して一連の getter 呼び出しを行うことをテストするのは意味がないため)。この場合、モック オブジェクトはスタブとして設定され、特定のパラメーターを使用した特定の一連の呼び出しを期待するのではなく、常に特定の結果セットを返します (スタブに関する PHP マニュアルのエントリを参照してください)。これは、コード内の '$this->at(x)' を '$this->any()' に置き換えるだけで実行できます。ただし、これにより、ポイント 2 で述べた余分な行を追加する必要がなくなりますが、それでも繰り返しがあります。コードの前にテストを作成する TDD アプローチに従って、次のことをお勧めします。

    public function testEdit() {
        $this->Controller = $this->generate('Posts', array(
            'components' => array(
                'Auth' => array('user')
            )
        ));
            $this->Controller->Auth
                ->staticExpects($this->any())
                ->method('user')
                ->will($this->returnValue(array(
                    'id' => 1,
                    'username' => 'admin',
                    'created' => '2013-05-08 00:00:00',
                    'modified' => '2013-05-08 00:00:00',
                    'email' => 'me@me.com',
                )));
    
        $this->testAction('/posts/edit/1', array('method' => 'get'));
    }
    

これにより、コントローラーを更新して、モック オブジェクトによって既に返されているユーザー属性を任意の順序で取得するために、好きなだけ呼び出しを行うことができます。コントローラーがそれらを取得するかどうか、および取得する頻度に関係なく、すべてのユーザー属性 (またはおそらくこのコントローラーに関連する可能性のあるすべて) を返すように、モック オブジェクトを作成できます。(特定の例で、モックに「email」が含まれている場合、コントローラーの pr() ステートメントはブラウザーとは異なる結果をテストから出力しますが、レコードに新しい属性を追加できるとは思わないことに注意してくださいテストを更新する必要はありません)。

このようにテストを書くということは、コントローラの編集機能が次のようなものである必要があることを意味します - よりテストしやすいバージョン:

$this->autoRender = false;

if (!$this->Post->exists($id)) {
    throw new NotFoundException(__('Invalid post'));
}

$user = $this->Auth->user();

if ($this->Post->find('first', array(
    'conditions' => array(
        'Post.id' => $id,
        'Post.user_id' => Hash::get($user, 'id')
    )
))) {
    echo 'Username: ' . Hash::get($user, 'username') . '<br>';
    echo 'Created: ' . Hash::get($user, 'created') . '<br>';
    echo 'Modified: ' . Hash::get($user, 'modified') . '<br>';
    echo 'All:';
    pr($user);
    echo 'Modified: ' . Hash::get($user, 'modified') . '<br>';
} else {
    echo 'Unauthorized.';
}

私が収集できる限り、Hash::get($record, $key) はレコードから属性を取得する正しい CakePHP の方法ですが、ここにある単純な属性では、user[$key] も同様に機能すると思います。

于 2013-05-10T01:05:10.467 に答える