ci-phpunit-testを使う時に知っておいて欲しいCodeIgniter3の実装-Modelの実装
Tweet私はしばし、ci-phpunit-testを使ったテストの書き方で、CodeIgniter3自体の挙動を説明しなければならないことがあります。
私は、その説明が非常にめんどくさいと感じているのでここに記載します。
知っておいて欲しい実装は次の4つです。
- Controllerの実装(設計)
- Controllerの実装(コンストラクタ)
- 依存性解決の実装
- Modelの実装 <- イマココ
Modelの実装
CodeIgniterのModelの実装は次の通りです。
1 |
|
本当に、これだけです。
マジックメソッドの__get
でプロパティが存在しない場合はControllerインスタンスのプロパティを利用する様にしています。
ここで重要になるのがマジックメソッドの__get
はプロパティが存在しない場合に動きます。
テスト時に知っておきたいこと
次の様なテストコードを実行した場合、Todo_modelの$this->todo_dao
はControllerインスタンスのtodo_daoプロパティを参照しているわけではないです。
テストコードでTodo_modelのtodo_daoプロパティに注入したMockオブジェクトを参照しています。
このとき、__get
メソッドは呼び出されていません。
モデル1
2
3
4
5
6
7
8
9
10
11
12
class Todo_model extends CI_Model
{
public function find(int $id)
{
// Loader::modelを用いているのでControllerインスタンスにtodo_daoプロパティが注入される。
$this->load->model('todo_dao');
// `__get`マジックメソッドによりControllerインスタンスのtodo_daoプロパティが利用される。
return $this->todo_dao->get($id);
}
}
テストコード1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Todo_model_test extends UnitTestCase
{
private $obj;
public function setUp()
{
parent::setUp();
$this->obj = $this->newModel('todo_model');
}
public function test_inject_todo_dao_property()
{
// Todo_modelのtodo_daoプロパティにMockオブジェクトを注入している。
// このプロパティへのMockオブジェクトの注入により、
// Todo_modelからtodo_daoへの参照時に__getは動作しません。
$this->obj->todo_dao = $this->getDouble(Todo_dao::class, [
'get' => ['id' => 1]
]);
$actual = $this->obj->find(1);
$this->assertSame(1, $actual['id']);
}
}
このため、前回のLoader::modelの説明では
Loader::modelはLoader::modelを利用せずにプロパティに代入されているとエラーが発生する
と記載しましたが、この場合は$this->load->model('todo_dao')
を呼び出していますが、エラーは発生しないです。
なぜならば、Loader::modelではControllerインスタンスのプロパティに対して、存在やインスタンス名がチェックされるからです。
つまり、以下の様なテストを書いたとしてもTodo_dao内でdbのmockオブジェクトが参照されることはありません。
Todo_dao1
2
3
4
5
6
7
8
9
10class Todo_dao extends CI_Model
{
public function get(int $id): array
{
$this->db->where('id', $id);
$query = $this->db->get('todo', 1);
return $query->row(0, 'array') ?? [];
}
}
Todo_modelのテストコード
1 |
|
総まとめ
Controllerの実装(設計)
- システム内で
get_instance
関数を用いて、Controllerの同一なインスタンスを利用しやすい様になっています。 - Controllerインスタンスは複数のインスタンスを生成することができます。
Controllerの実装(コンストラクタ)
- load_class関数を用いてインスタンス化されたオブジェクトをControllerのプロパティに代入する。
parent::__construct
より前で$this->load
は利用できない。- 最後にインスタンス化したControllerが
get_instance
で取得できる様になる。
依存性解決の実装
- load_class関数はLibraryやsystem/coreディレクトリのクラスを読み込むために利用されている。
- Loader::model、Loader::library実行時はinstanceofでインスタンス名がチェックされている。
- Loader::modelはLoader::modelを利用せずにプロパティに代入されているとエラーが発生する。
Modelの実装
- Modelはプロパティが存在しないときにControllerインスタンスのプロパティを参照する。
最後に
この中でも特に重要なのはCodeIgniter3というフレームワークは、Controllerインスタンスのプロパティへ依存性を注入して依存関係を解決しています。
そのフレームワークの機能を用いた依存性の注入が行われる経路は大きく分けてload_class
とLoader
があります。
- どのControllerインスタンスが利用されているか。
- どの経路から注入することができるか。
- どの様に注入することでテストが動くか。
これらの知識は、依存関係の解決が複雑なControllerのテスト時に役にたちます。
これらを覚えてモリモリci-phpunit-testでテストを書きましょう。
また、Loaderの存在しないCodeIgniter4では、Controllerのテスト時しやすいようにコンストラクタでの依存関係解決は行われていないです。
これは、また別の機会に。
現場からは以上でした。