ci-phpunit-testを使う時に知っておいて欲しいCodeIgniter3の実装-Controllerの実装(設計)
Tweet私はしばし、ci-phpunit-testを使ったテストの書き方で、CodeIgniter3自体の挙動を説明しなければならないことがあります。
私は、その説明が非常にめんどくさいと感じているのでここに記載します。
知っておいて欲しい実装は次の4つです。
- Controllerの実装(設計) <- イマココ
- Controllerの実装(コンストラクタ)
- 依存性解決の実装
- Modelの実装
Controllerの実装(設計)
ControllerはSingletonのような神クラスです。
Sigletonとは
Sigletonとは、そのクラスのインスタンスが一つしか保証されないクラスです。
おそらく実装を見た方が理解が早いと思います。
次のようなクラスが存在する場合は、getInstanceメソッドを経由してしかSingletonクラスをインスタンス化できません。
また、newやcloneは利用できず、PHPを実行するプロセスの中でインスタンスが一つしか生成することができません。
1 | class Singleton |
1 | // コンストラクタがprivateなのでエラーが発生します |
Controllerは神クラス
CodeIgntierのControllerは上記で説明したSingletonに似た神クラスです。
Singletonと違い、cloneやnewを行うことができます。
1 | // コンストラクタがpublicなのでエラーが発生しません。 |
つまり、CI_Controllerは一意でない可能性はあり得る構造になっています。
しかし、システム内では基本的にget_instance
関数を用いてSingletonの様に扱っています。
Singletonの様に扱うことで現在ユーザからアクセスされているControllerと同一のインスタンスを利用しやすい設計になっています。
ここまでのまとめ
- システム内で
get_instance
関数を用いて、Controllerの同一なインスタンスを利用しやすい様になっています。 - Controllerインスタンスは複数のインスタンスを生成することができます。
テスト時に知っておきたいこと
実際にテストコードに落とし込んで知っておきたい事を記します。
次の例は、Controllerのテストコードです。
テスト対象のController1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Test_target extends CI_Controller
{
public function __construct()
{
parent::__construct();
$this->load->model('todo_model');
}
public function list()
{
$list = $this->todo_model->get_list();
$this->output->enable_profiler(false)
->set_content_type('application/json; charset=UTF-8')
->set_output(json_encode(
$list,
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
));
}
}
Controllerの同一なインスタンスを利用しやすい様になっている
ci-phpunit-testでは実際にCodeIgniterで作成したアプリケーションへリクエストを行なった時と同じ様に振る舞うrequest
メソッドがあります。
requestメソッドを行う際に、テストコード実装者が意図した振る舞いをするMockオブジェクトに差し替えたいと考えるでしょう。
そんな時は、Controllerのメソッドが呼び出される前処理を挟むsetCallable
を利用すると良いです。
setCallable
では引数に渡した関数の第一引数としてControllerインスタンスが渡されます。
このControllerインスタンスは、テスト対象のリクエストされるControllerインスタンスと同一です。
第一引数へ渡された、ControllerインスタンスのプロパティにMockオブジェクトを注入することができます。
1 |
|
Controllerインスタンスは複数のインスタンスを生成することができる
先ほどとは打って変わって、次のコードではテスト対象のController(Target_test)とは異なるControllerインスタンスへMockオブジェクトを注入してしまっている例です。
このテストではMockオブジェクトがテスト対象のController(Target_test)からアクセスされることはありません。
1 |
|
これは、Test_targetコントローラのインスタンスと、$this->CI
で持っているControllerインスタンスが異なるためです。