hirapi's blog

ちゃんとしたふりをする

(社内LT資料)ユニットテスト:ちょっとしたDI

週1回、朝会後に簡単なLTをやることにした。
(残りの4日間は広告業界・アドテク分野のニュースを適当にひっぱってみんなで眺める会)

ひとつのテーマで2〜3人がしゃべる形式。
記念すべき第1回のテーマは「ユニットテスト」。

あえて少し外して、むかし先輩に教えてもらったDIと絡めて話してみた。
併せて見せたサンプルコードは下に。
(普段ユニットテストPHPUnit使って書いてるけど今回はライブラリにフォーカスしたくなかったので自前で)

ちょっとしたDI - slideship.com

サンプルコード

悪い例

改修前

<?php

// コントローラ

$team = new Team();
echo $team->doAsakai(); // 今日も一日がんばりましょう


// モデル

class Team {

    private $leader;

    public function __construct() {
        // リーダーは事業マネージャー
        $this->leader = new ServiceManager();
    }

    public function doAsakai() {
        return 'リーダーより:' . $this->leader->sayAsakaiMessage();
    }
}

class ServiceManager {

    public function sayAsakaiMessage() {
        return '今日も一日がんばりましょう';
    }
}


// テスト

class TeamTest {

    public function execute() {
        $team = new Team();

        echo 'doAsakai()のテスト';

        if ($team->doAsakai() === 'リーダーより:今日も一日がんばりましょう') {
            echo 'OK!!';
        } else {
            echo 'NG!!';
        }
    }
}

改修後

<?php

// コントローラ

$team = new Team();
echo $team->doAsakai(); // にゃーん


// モデル

class Team {

    private $leader;

    public function __construct() {
        // リーダーはエンジニアマネージャー
        $this->leader = new EngineerManager(); // <- 改修
    }

    public function doAsakai() {
        return 'リーダーより:' . $this->leader->sayAsakaiMessage(); // <- 出力が変わった
    }
}

class ServiceManager {

    public function sayAsakaiMessage() {
        return '今日も一日がんばりましょう';
    }
}

class EngineerManager {

    public function sayAsakaiMessage() {
        return 'にゃーん';
    }
}


// テスト

class TeamTest {

    public function execute() {
        $team = new Team();

        echo 'doAsakai()のテスト';

        if ($team->doAsakai() === 'リーダーより:にゃーん') { // <- テストも変える
            echo 'OK!!';
        } else {
            echo 'NG!!';
        }
    }
}

すこし良い例

改修前

<?php

// コントローラ

$leader = new ServiceManager(); // リーダーは事業マネージャー
$team = new Team($leader);
echo $team->doAsakai();


// モデル

class Team {

    private $leader;

    public function __construct($leader) {
        // リーダーは誰でもいい
        $this->leader = $leader;
    }

    public function doAsakai() {
        return 'リーダーより:' . $this->leader->sayAsakaiMessage(); // leader の sayAsakaiMessage() と同じ挙動であればOK
    }
}

class ServiceManager {

    public function sayAsakaiMessage() {
        return '今日も一日がんばりましょう';
    }
}


// テスト

// sayAsakaiMessage() を持ったモッククラス
class ManagerMock {
    public function sayAsakaiMessage() {
        return '私は sayAsakaiMessage() メソッドを持っています';
    }
}

class TeamTest {

    public function execute() {
        $leader = new ManagerMock();
        $team = new Team($leader);

        echo 'doAsakai()のテスト';

        if ($team->doAsakai() === 'リーダーより:私は sayAsakaiMessage() メソッドを持っています') {
            echo 'OK!!';
        } else {
            echo 'NG!!';
        }
    }
}

改修後

<?php

// コントローラ

$leader = new EngineerManager(); // リーダーはエンジニアマネージャー <- 改修はここだけ
$team = new Team($leader);
echo $team->doAsakai();


// モデル

class Team {

    private $leader;

    public function __construct($leader) {
        // リーダーは誰でもいい
        $this->leader = $leader;
    }

    public function doAsakai() {
        return 'リーダーより:' . $this->leader->sayAsakaiMessage();
    }
}

class ServiceManager {

    public function sayAsakaiMessage() {
        return '今日も一日がんばりましょう';
    }
}

class EngineerManager {

    public function sayAsakaiMessage() {
        return 'にゃーん';
    }
}


// テスト

// sayAsakaiMessage() を持ったモッククラス
class ManagerMock {
    public function sayAsakaiMessage() {
        return '私は sayAsakaiMessage() メソッドを持っています';
    }
}

class TeamTest {

    public function execute() {
        $leader = new ManagerMock();
        $team = new Team($leader);

        echo 'doAsakai()のテスト';

        if ($team->doAsakai() === 'リーダーより:私は sayAsakaiMessage() メソッドを持っています') { // <- テストも変わらない
            echo 'OK!!';
        } else {
            echo 'NG!!';
        }
    }
}

良い例

改修前

<?php

// コントローラ

$leader = new ServiceManager(); // リーダーは事業マネージャー
$team = new Team($leader);
echo $team->doAsakai();


// インタフェース

interface Manager {
    public function sayAsakaiMessage();
}


// モデル

class Team {

    private $leader;

    public function __construct(Manager $leader) { // <- インタフェースで型宣言
        // リーダーは誰でもいい
        $this->leader = $leader;
    }

    public function doAsakai() {
        return 'リーダーより:' . $this->leader->sayAsakaiMessage(); // Managerインタフェースの実装クラスは必ずsayAsakaiMessage()を持っている
    }
}

class ServiceManager implements Manager { // <- Managerインタフェースを実装

    public function sayAsakaiMessage() {
        return '今日も一日がんばりましょう';
    }
}


// テスト

// sayAsakaiMessage() を持ったモッククラス
class ManagerMock implements Manager { // <- Managerインタフェースを実装
    public function sayAsakaiMessage() {
        return '私は sayAsakaiMessage() メソッドを持っています';
    }
}

class TeamTest {

    public function execute() {
        $leader = new ManagerMock();
        $team = new Team($leader);

        echo 'doAsakai()のテスト';

        if ($team->doAsakai() === 'リーダーより:私は sayAsakaiMessage() メソッドを持っています') {
            echo 'OK!!';
        } else {
            echo 'NG!!';
        }
    }
}

改修後

<?php

// コントローラ

$leader = new EngineerManager(); // リーダーはエンジニアマネージャー
$team = new Team($leader);
echo $team->doAsakai();


// インタフェース

interface Manager {
    public function sayAsakaiMessage();
}


// モデル

class Team {

    private $leader;

    public function __construct(Manager $leader) { // <- インタフェースで型宣言
        // リーダーは誰でもいい
        $this->leader = $leader;
    }

    public function doAsakai() {
        return 'リーダーより:' . $this->leader->sayAsakaiMessage(); // Managerインタフェースの実装クラスは必ずsayAsakaiMessage()を持っている
    }
}

class ServiceManager implements Manager { // <- Managerインタフェースを実装

    public function sayAsakaiMessage() {
        return '今日も一日がんばりましょう';
    }
}

class EngineerManager implements Manager { // <- Managerインタフェースを実装

    public function sayAsakaiMessage() {
        return 'にゃーん';
    }
}


// テスト

// sayAsakaiMessage() を持ったモッククラス
class ManagerMock implements Manager { // <- Managerインタフェースを実装
    public function sayAsakaiMessage() {
        return '私は sayAsakaiMessage() メソッドを持っています';
    }
}

class TeamTest {

    public function execute() {
        $leader = new ManagerMock();
        $team = new Team($leader);

        echo 'doAsakai()のテスト';

        if ($team->doAsakai() === 'リーダーより:私は sayAsakaiMessage() メソッドを持っています') {
            echo 'OK!!';
        } else {
            echo 'NG!!';
        }
    }
}