やはりあなた方のDependency Injectionはまちがっている。

今日はPHP界隈で大人気のDependency Injectionと、それに関連する用語について整理しておこうと思います。

以下のような状況があるのではないか?と思ったからです。

  • 多くのPHPユーザがDependency Injection(DI)をよくわかっていない、あるいは正確に説明できません。
  • そして、デザインパターンである「DIパターン」とDIをサポートするツールである「DIコンテナ」を混同しています。
  • また、「DIパターン」と「サービスロケータパターン」をうまく区別できていません。

Dependency Injectionとは何か?

Dependency Injectionとは「Dependency」を「Injection」するというデザインパターンです。

日本語では何故か「依存性の注入」と訳されており、これが混乱の元ではないかと思います。

日本語で「依存性」と言うと、「依存性はコカイン並み」「ニコチンは依存性薬物」のような用法がほとんどで、「依存する性質」というような意味になります。

そのような「依存性」を「注入する」と言うと、もうかなり意味不明ですね。

たぶん、多くの人は「依存性」を「依存関係」のように理解しているのではないかと思います。「依存関係を注入する」だと少しましな気はします。

ただ、やはり日本語としては意味不明で、また、そのような理解が誤解の一因になっている気がします。

「注入する」という場合、もっと具体的な物が必要です。

「Dependency」は「依存性」ではない

それでは、Wikipdediaの説明を見てみましょう。

A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client's state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
https://en.wikipedia.org/wiki/Dependency_injection

ここでは、明確に「dependency」とは「オブジェクト」であると定義されています。

「オブジェクトを注入する」と言うと日本語としても意味がはっきりしていますね。

Wikipediaによると「dependency」は使われるオブジェクト(サービスと呼ぶ)であり、「injection」とはそのオブジェクト(=サービス)を、それを使うオブジェクト(クライアントと呼ぶ)に渡すことです。

ちょっとわかりにくいですが、簡単に言うと、あるオブジェクト(=サービス)を別のオブジェクト(=クライアント)に渡すパターンがDIパターンです。

ちなみに、「サービス」と「クライアント」という用語は、関係性を表現しているだけです。使う方を「クライアント(依頼者)」、使われる方が「サービス」になります。

Wikipediaだけでは信用できないという人もいるかも知れませんので、他にも調べてみましょう。

マイクロソフトのランゲージポータルには、「dependency」について以下の説明があります。

For example, if feature A depends on feature B, B is a dependency of A. https://www.microsoft.com/Language/ja-jp/Search.aspx?sString=dependency&langID=ja-jp

「機能Aが機能Bに依存している場合、BがAのdependencyである」と記載されています。

また、Dependency Injectionという用語を作成したと言われるマーティン・ファウラーの文章の翻訳でも、「dependency」は「依存オブジェクト」と訳されています。

結論をいえば、このパターンにはもっと明確な名前が必要なように思う。 「制御の反転」という用語では包括的すぎる。これでは混乱する人が出てくるのも無理はない。 様ざまな IoC 支持者と多くの議論を重ねた末、その名前は Dependency Injection (依存オブジェクト注入)に落ち着いた。
http://kakutani.com/trans/fowler/injection.html

以上のように、「Dependency」は「依存性」ではなく「使われる側のオブジェクト」(依存オブジェクト)という意味になります。

DIパターンのコード例

コードを見た方がわかりやすいかも知れません。以下のようなパターンがDIです。

<?php

class Client
{
    private $service;

    public function __construct(Service $service)
    {
        $this->service = $service;
    }

    public function doSomething()
    {
        $this->service->doSomething();
    }

    ...
}

class Service
{
    public function doSomething()
    {
        ...
    }

    ...
}

Clientクラスがクライアント、Serviceクラスがサービス(=dependency)です。

DIはデザインパターンなので、あるオブジェクトが外部から渡されていればそれはDIパターンです。

上記のコードではコンストラクタを使ってサービスを注入してるため、「コンストラクタインジェクション」と呼ばれます。

他にも、注入するためのメソッドを用意する「セッターインジェクション」や、プロパティに直接注入する「プロパティインジェクション」という方法もあります。

で、DIって何なの?

デザインパターンです。

DIパターンでないコード例

先ほどのコードをDIを使わないように書き換えると、以下のようになります。

<?php

class Client
{
    private $service;

    public function __construct()
    {
        $this->service = new Service();
    }

    public function doSomething()
    {
        $this->service->doSomething();
    }

    ...
}

class Service
{
    public function doSomething()
    {
        ...
    }

    ...
}

たぶん、この方が馴染みのあるコードかも知れません。

この場合、Clientクラス内でServiceオブジェクトが生成(new)されています。

一方、DIパターンの方は、Serviceオブジェクトは外部で生成され、Clientクラスに注入されることになります。

DIパターンの特徴

DIパターンでは、

  1. オブジェクトの生成と使用が分離されている
  2. クライアントがサービスを呼ぶのではなく、サービスが外部からクライアントに注入される。つまり、制御が反転している

と言えます。

例えば、ServiceがPDOオブジェクトだとして、DIパターンの場合は、Clientクラスのコードを一切変更せずに、

  • あるClientオブジェクトにはmasterに接続するPDOオブジェクトを
  • 別のClientオブジェクトにはslaveに接続するPDOオブジェクトを

注入することができます。

依存とは何か?

ここで、「依存」あるいは「依存している」という言葉について整理しておきます。

「オブジェクトAがオブジェクトBに依存している」とは、「オブジェクトAが内部でオブジェクトBを使用している」ということです。

以下のコード(先ほどのDIパターンでないコード例)の場合、ClientはServiceに依存しています。

<?php

class Client
{
    private $service;

    public function __construct()
    {
        $this->service = new Service();
    }

    public function doSomething()
    {
        $this->service->doSomething();
    }

    ...
}

class Service
{
    public function doSomething()
    {
        ...
    }

    ...
}

ClientオブジェクトはServiceオブジェクトがないと動作できません。これが「依存している」ということです。

DIパターンの場合でも、やはりClientはServiceに依存しています。ClientオブジェクトはServiceオブジェクトがないと動作できないからです。

DIパターンを使ったからと言って、依存がなくなるわけではありません。

抽象に依存せよ

ただし、DIパターンを使った場合は、以下のようなコーディングも可能になります。

<?php

class Client
{
    private $service;

    public function __construct(ServiceInterface $service)
    {
        $this->service = $service;
    }

    public function doSomething()
    {
        $this->service->doSomething();
    }

    ...
}

interface ServiceInterface
{
    public function doSomething();
    ...
}

class Service implements ServiceInterface
{
    public function doSomething()
    {
        ...
    }

    ...
}

この場合、ClientはServiceInterfaceインターフェイスを実装したオブジェクトがあれば動作できます。

Serviceオブジェクトでなくても別のServiceInterfaceインターフェイスを実装したオブジェクトがあればいいということになります。

このような場合は、「抽象に依存している」と言います。

Clientは、具象(コンクリート)クラスには依存していませんが、インターフェイスを実装したクラスに依存しています。

抽象に依存するようにすれば、サービスをDIで別の実装(クラス)に差し替えることができ、クライアントクラスとサービスクラスの結合度が緩くなります(疎結合)。

逆に、DIパターンでないコード例のようなクラス内でnewしているコードの場合、クライアントクラスとサービスクラスの結合度がきついです(密結合)。

DIコンテナとは?

DIパターンについて上記で解説しました。

このパターンは「使われるオブジェクト(=サービス)」を外部からクライアントに注入するというパターンなので、クライアントクラスの外部でサービスオブジェクトを生成し注入すれば、それはDIパターンを使っている(=DIしている)と言えます。

Clientクラスを使うコードは、以下のようになります。これを「手動でのDI」と言います。

$service = new Service();
$client = new Client($service);

この程度のコードならこれで何の困難もありません。しかし、オブジェクトがたくさんあり依存関係が複雑になると、オブジェクト生成のコードも複雑になり大変になります。

そこで、DIを楽にしたり、オブジェクト生成のコードをまとめるための便利なツールとして「DIコンテナ」が登場します。

シンプルなDIコンテナである Pimple を使うと、コードは以下のようになります。

use Pimple\Container;

$container = new Container();

// オブジェクト生成の設定
$container['service_interface'] = function ($container) {
    $service = new Service();
    return $service;
};
$container['client'] = function ($container) {
    $client = new Client($container['service_interface']);
    return $client;
};

// オブジェクトの使用
$client = $container['client'];

これが、DIコンテナを使ったDIです。

元のコードがシンプル過ぎるので、単にコードが増えて面倒になっただけに感じるかも知れませんが、もしクラスが増えてくると便利になる気がしませんか?

もし便利になる気がしないなら、あなたのプロジェクトにはDIコンテナはまだ必要ないということでしょう。無理にDIコンテナを使う必要はありません。DIコンテナがなくてもDIは使えます。

サービスロケータとは何か?

ところで、DIコンテナを使えば自動的にDIパターンになるかというと、答えはNoです。

なぜなら、DIコンテナを使うと以下のようなコードも書けるからです。

use Pimple\Container;

class Client
{
    private $container;

    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    public function doSomething()
    {
        $this->container['service_interface']->doSomething();
    }

    ...
}

interface ServiceInterface
{
    public function doSomething();
    ...
}

class Service implements ServiceInterface
{
    public function doSomething()
    {
        ...
    }

    ...
}

Clientクラスを使うコードは、以下のようになります。

use Pimple\Container;

$container = new Container();

// オブジェクト生成の設定
$container['service_interface'] = function ($container) {
    $service = new Service();
    return $service;
};
$container['client'] = function ($container) {
    $client = new Client($container);
    return $client;
};

// オブジェクトの使用
$client = $container['client'];

大きな違いは、Clientクラスがコンテナに依存していることです。

「サービスロケータ」とは、サービス(オブジェクト)の取得を抽象化するデザインパターンです。あるアプリケーションが必要とするサービスのすべてを保持するレジストリ(サービスロケータ)を用意します。

上記のコードでの$containerがまさにサービスロケータです。

サービスロケータはアンチパターンである

PHP: The Right Way はサービスロケータについて以下のように説明しています。

DIコンテナは、依存性の注入を実現するための便利な道具として使える。 でも、使い方をミスって、サービスロケーションというアンチパターンを作ってしまっていることも多い。 DIコンテナをサービスロケーターとしてクラスに組み込んでしまうと、 依存関係を別の場所に移そうとしていたはずなのに、よりきつい依存関係を作り込むことになる。 おまけにそのコードはわかりにくくなってしまうし、テストもしづらくなる。

なんだかサービスロケータはいいことなしのダメパターンのような言われ方ですね。

サービスロケータの何が悪いのか?

以下のDIの場合のコードと比べてサービスロケータの場合、何がそんなに悪いのでしょうか?

DIの場合

class Client
{
    private $service;

    public function __construct(ServiceInterface $service)
    {
        $this->service = $service;
    }

    public function doSomething()
    {
        $this->service->doSomething();
    }

    ...
}

サービスロケータの場合

use Pimple\Container;

class Client
{
    private $container;

    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    public function doSomething()
    {
        $this->container['service_interface']->doSomething();
    }

    ...
}

まず、DIの場合、Clientクラスが依存しているのはServiceInterface(を実装したオブジェクト)のみです。

サービスロケータの場合は、コンテナ(Pimple)と$this->container['service_interface']に依存しています。

依存するオブジェクトが増える

つまり、サービスロケータの場合、依存するオブジェクトが1つ増えています。

この場合のClientクラスはコンテナがないと動作できません。しかし、本来Clientクラスが必要なのはServiceInterface(を実装したオブジェクト)のみです。

仮にClientクラスがあるライブラリだとすると、そのライブラリはコンテナ(この場合はPimple)と一緒でないと使えません。例えば、自分の使っているフレームワークが別の(Pimpleとインターフェイス互換でない)コンテナを使っていると、そのライブラリを使うためだけにPimpleをインストールする必要が生じます。

このようにサービスロケータを使うと、本来必要ないコンテナへの依存が発生してしまいます。

テストが面倒になる

このクラスをテストするには、コンテナが必ず必要です。コンテナをテストダブルで置き換えることもできますが、DIの場合ならそもそもコンテナは不要です。このようにサービスロケータを使うとテストが少し面倒になります。

依存するオブジェクトが何かわからない

それから、サービスロケータの場合で依存している$this->container['service_interface']とは何でしょうか?

これはコンテナでの定義を調べないとわかりません。つまり、コードが読みにくくなっています。

一方、DIの場合なら、Clientクラスのコードを見ただけで、ServiceInterfaceインターフェイスに依存していることがすぐにわかります。

さらに、コンテナにはすべてのサービスが登録されているため、どんなサービスでもいくらでもそこから取得できます。本来依存すべきでないサービスに依存するコードも簡単に書けますし、そのクラスが何に依存しているかもわかりづらいコードになります。

上記のような理由から、「PHP: The Right Way」ではサービスロケータをアンチパターンである、としています。

DIとサービスロケータの区別

サービスロケータでもサービスの差し替えは可能

以上のようなデメリットのあるサービスロケータですが、コンテナでの定義を変更することでサービスオブジェクトを差し替えることができる点は、DIと変わりません。

テスト時にサービスを簡単にテストダブルに差し替えることもできます。

つまり、クライアントのコードを一切変更することなしに、クライアントが使用するオブジェクトを差し替えることで振る舞いを変えることができます。

ということで両者は似ており、区別しづらい面があることも確かです。

DIとサービスロケータの区別の方法

最後に Quicker, Easier, More Seductive: Names, Usage, and Intent | Paul M. Jones より、DIとサービスロケータの区別の方法を紹介しておきます。

  • コンテナをファクトリでないオブジェクトの 外側 で使う場合、あなたはコンテナをDIのために使っている。
  • コンテナをファクトリでないオブジェクトの 内側 で使う場合、あなたはコンテナをサービスロケータとして使っている。

まとめ

  • DIはデザインパターンであり、ツールであるDIコンテナとは別の概念です。
  • Dependency InjectionのDependencyは依存性ではありません。「使われるオブジェクト」(依存オブジェクト)という意味です。
  • DIコンテナを使っていれば、必ずDIパターンになるわけではありません。
  • DIとサービスロケータは別のデザインパターンです。
  • サービスロケータはアンチパターンです。

参考

Tags: php, programming

CodeIgniterとPHPUnitでテストが難しいコードを簡単にテストする(オブジェクトメソッドの置換)

従来、CodeIgniterのアプリはテストが書きにくいと言われてきました。

しかし、最新のCodeIgniter 3.0とPHPUnitおよびブリッジツールであるci-phpunit-testを使うと、ユニットテストが難しいと言われるコードのテストも驚くほど簡単に記述することができます。

【注】ここでは、1クラスを1つのユニットと考えることにします。

CodeIgniterとPHPUnitの準備

イマドキのCodeIgniterでPHPUnit入門 - Qiita」での環境を使います。

GitHubにあるソースを使う場合は、以下のようにします。

$ git clone https://github.com/kenjis/imadoki-codeigniter-phpunit.git
$ cd imadoki-codeigniter-phpunit/
$ composer install

ただし、ci-phpunit-testを最新の開発版にアップデートしておきます。これは、libraryのオートローダを使いたいためです(libraryのオートローダはv0.7.0以降に含まれる予定です)。

$ composer require kenjis/ci-phpunit-test:1.0.x@dev
$ php vendor/kenjis/ci-phpunit-test/update.php

テスト対象のクラス

ユニットテストが難しいケースとしてFooクラスを作成します。

このクラスは「PHP - 依存性注入(DI)の解説とやり方 - Qiita」より借用しています。

application/libraries/Foo.php

<?php

class Foo
{
    public function play()
    {
        $bar = new Bar();

        if ($bar->getSomething() === 1) {
            return true;
        }

        return false;
    }
}

FooクラスはBarクラスに依存しており、Fooの中でBarnewされています。

依存しているBarクラスも用意しておきます。

application/libraries/Bar.php

<?php

class Bar
{
    public function getSomething()
    {

    }
}

テストケースクラスの作成

それでは、Fooクラスのテストを作成してみましょう。

application/tests/libraries/Foo_test.php

<?php

class Foo_test extends TestCase
{
    public function setUp()
    {
        $this->obj = new Foo();
    }

    public function test_play_true()
    {
        MonkeyPatch::patchMethod('Bar', ['getSomething' => 1]);

        $this->assertTrue($this->obj->play());
    }

    public function test_play_false()
    {
        MonkeyPatch::patchMethod('Bar', ['getSomething' => 0]);

        $this->assertFalse($this->obj->play());
    }
}

ここでは、ci-phpunit-testのモンキーパッチ機能を使い、BarクラスのgetSomething()メソッドに介入しています。モンキーパッチとはオリジナルのソースコードを変更することなく、実行時にコードを変更することです。

最初のテストメソッドtest_play_true()では、BarクラスのgetSomething()メソッドの返り値を1に変更しています。

MonkeyPatch::patchMethod('Bar', ['getSomething' => 1]);

Barクラスをテストダブルで置き換えている、あるいはBarクラスのモック化と言うこともできるでしょう。

Bar::getSomething()メソッドが1を返すので、Foo::play()メソッドの返り値はtrueになるはずです。

次のテストメソッドtest_play_false()では、BarクラスのgetSomething()メソッドの返り値を0に変更しています。

MonkeyPatch::patchMethod('Bar', ['getSomething' => 0]);

それでは、テストを実行してみましょう。

$ cd application/tests/
$ ../../vendor/bin/phpunit libraries/Foo_test.php
PHPUnit 4.8.5 by Sebastian Bergmann and contributors.

..

Time: 695 ms, Memory: 6.25Mb

OK (2 tests, 2 assertions)

Generating code coverage report in Clover XML format ... done

Generating code coverage report in HTML format ... done

通りました。

このようにユニットテストに必須なことは、依存性注入(DI)ではなく、依存するオブジェクトや依存するクラスをテストダブルで置き換えることです。

まとめ

  • CodeIgniter 3.0とPHPUnit、ci-phpunit-testを使うとテストが難しいと言われるコードのテストも驚くほど簡単に記述することができます。
  • CodeIgniterのアプリはテストが書きにくいというのは過去の話です。
  • テストを書くためだけに依存性注入(DI)を使う必要はありません。

ただし、テストが書けるということと、それが良いテストであることはイコールではありませんし、テスト対象クラスの設計が良いかどうかも別の話です。

依存性注入(DI)が有効なケースもあるでしょうし、そうでない場合もあるでしょう。逆にDIを使えば自動的に良い設計というわけでもないと思います。

良くないテストはリファクタリングを妨げます。しかし、良くないテストでもテストが全くないよりはいいです。

関連

Tags: phpunit, codeigniter, testing

ci-phpunit-testを使って和田卓人さんの『現在時刻に依存するテスト』を書いてみる

2013年に以下のテストに関する記事がありました。

後藤さんが解答されていたやつです。

今日は、この『現在時刻に依存するテスト』をci-phpunit-testを使って書いてみようと思います。

セットアップ

CodeIgniter 3.0をインストールします。

$ composer create-project kenjis/codeigniter-composer-installer ci-tdd

ci-phpunit-testをインストールします。

$ cd ci-tdd/
$ composer require kenjis/ci-phpunit-test --dev
$ php vendor/kenjis/ci-phpunit-test/install.php

モンキーパッチ機能を有効にします。

--- a/application/tests/Bootstrap.php
+++ b/application/tests/Bootstrap.php
@@ -295,7 +295,6 @@ switch (ENVIRONMENT)
  * If you want to use monkey patching, uncomment below code and configure
  * for your application.
  */
-/*
 require __DIR__ . '/_ci_phpunit_test/patcher/bootstrap.php';
 MonkeyPatchManager::init([
        'cache_dir' => APPPATH . 'tests/_ci_phpunit_test/tmp/cache',
@@ -320,7 +319,6 @@ MonkeyPatchManager::init([
        ],
        'exit_exception_classname' => 'CIPHPUnitTestExitException',
 ]);
-*/

 /*
  * -------------------------------------------------------------------

これで準備完了です。

問題

【仕様1】 「現在時刻」に応じて、挨拶の内容を下記のようにそれぞれ返す機能を作成したい。 (タイムゾーンはAsia/Tokyoとする)

  • 朝(05:00:00以上 12:00:00未満)の場合、「おはようございます」と返す
  • 昼(12:00:00以上 18:00:00未満)の場合、「こんにちは」と返す
  • 夜(18:00:00以上 05:00:00未満)の場合、「こんばんは」と返す

例: 13時にgreeter.greet()を呼ぶと”こんにちは”と返す

これが問題です。

Greeterモデル

TDDでなくてすみません。Greeterモデルを先に作成します。

application/models/Greeter.php

<?php

class Greeter
{
    public function greet()
    {
        $hour = date('H');

        if ($hour >= 5 && $hour < 12) {
            return 'おはようございます';
        } elseif ($hour >= 12 && $hour < 18) {
            return 'こんにちは';
        } else {
            return 'こんばんは';
        }
    }
}

どうでしょうか?意図は明確でしょうか?

素朴に素直に、ある意味あまり考えずに書くと、このようになるのではないかと思います。

Greeterモデルのテスト

それではGreeterモデルのテストを書いてみましょう。

application/tests/models/Greeter_test.php

<?php

class Greeter_test extends TestCase
{
    public function setUp()
    {
        $this->resetInstance();
        $this->CI->load->model('Greeter');
        $this->obj = $this->CI->Greeter;
    }

    /**
     * @dataProvider provide_hours
     */
    public function test_greet($hour, $msg)
    {
        MonkeyPatch::patchFunction('date', $hour, 'Greeter');

        $actual = $this->obj->greet();
        $this->assertEquals($msg, $actual);
    }

    public function provide_hours()
    {
        return [
            ['00', 'こんばんは'],
            ['04', 'こんばんは'],
            ['05', 'おはようございます'],
            ['11', 'おはようございます'],
            ['12', 'こんにちは'],
            ['17', 'こんにちは'],
            ['18', 'こんばんは'],
            ['23', 'こんばんは'],
        ];
    }
}

ポイント1 TestCaseクラスを継承する

TestCaseクラスを継承すると(PHPUnitおよび)ci-phpunit-testの様々な機能を使えます。

ポイント2 setUp()メソッドでのテストの準備

ここで、モデルクラスのロードおよびインスタンス化をしています。CodeIgniter風のコードです。

もし、これが気に入らない人は、Composerのオートローダを使うようにして、

$this->obj = new App\Model\Greeter();

みたいにしてください。

ポイント3 パラメタライズドテスト

PHPUnitの@dataProviderアノテーションを使いパラメタライズドテストをしています。

指定したメソッドの返り値が、順にテストメソッドの引数に渡され、テストが実行されます。

ポイント4 関数へのモンキーパッチ

ci-phpunit-testのモンキーパッチ機能(関数パッチ)を使い、Greeterクラス内のdate()関数の返り値を動的に変更しています。

MonkeyPatch::patchFunction('date', $hour, 'Greeter');

【注意】この機能はすべての関数を変更できるわけではありません。詳細は、ドキュメント(英語)を参照してください。

テストの実行

それでは、テストを実行してみましょう。

phpunitコマンドはapplication/testsフォルダに移動して実行してください。

$ cd application/tests/
$ phpunit --debug models/Greeter_test.php
PHPUnit 4.7.7 by Sebastian Bergmann and contributors.
Warning:    The Xdebug extension is not loaded
        No code coverage will be generated.


Starting test 'Greeter_test::test_greet with data set #0 ('00', 'こんばんは')'.
.
Starting test 'Greeter_test::test_greet with data set #1 ('04', 'こんばんは')'.
.
Starting test 'Greeter_test::test_greet with data set #2 ('05', 'おはようございます')'.
.
Starting test 'Greeter_test::test_greet with data set #3 ('11', 'おはようございます')'.
.
Starting test 'Greeter_test::test_greet with data set #4 ('12', 'こんにちは')'.
.
Starting test 'Greeter_test::test_greet with data set #5 ('17', 'こんにちは')'.
.
Starting test 'Greeter_test::test_greet with data set #6 ('18', 'こんばんは')'.
.
Starting test 'Greeter_test::test_greet with data set #7 ('23', 'こんばんは')'.
.

Time: 189 ms, Memory: 3.75Mb

OK (8 tests, 8 assertions)

すべて通りました。グリーンです。

まとめ

  • ci-phpunit-testを使うとCodeIgniterアプリのテストを簡単に書くことができます。
  • たぶんテストできないものはありません。あれば、教えて下さい。
  • テストの書き方は1つではありません。いろいろな方法があります。

CodeIgniter 3.0でテストを書きたいという人は、是非、ci-phpunit-testを試してみてください。

なお、このようなコーディングを積極的に推奨しているわけではありません。

とはいえ、

  • 組み込みクラス/メソッド/関数に介入する

というテスト手法は以下の和田さんの記事にも出てくる一般的な方法です。言語機能的な制約からPHPではそれほど一般的でないだけです。

以下の記事をしっかり読むといろいろと参考になると思います。

関連

Tags: codeigniter, phpunit, testing