privateメソッドのユニットテストは書くの?書かないの?

privateメソッドのユニットテストを書きたいんだけど

privateメソッドのユニットテストは書いたほうがいいのかな?

privateメソッドのユニットテストは書かなくてもいい、という意見が主流みたいね

でもさ、外部から呼ばれるメソッドを書いていると、小さいメソッドがたくさんできるよね

この小さいメソッドは、外部から呼ばれないから、privateにしたいんだけど、PHPでprivateにすると、ユニットテストを書けないんだよ

でも、ユニットテストで何度も救われたことがあるから、ユニットテストを書いたほうがいいと思うんだ

さっきの和田さんの回答には、privateメソッドをテストする4つの方法が書かれているわよ

privateメソッドをテストする4つの方法

パブリックメソッド経由でテストする

1つめは「パブリックメソッド経由でテストする」ね

class Foo
{
    public function bar()
    {
        $c = $this->tiny($a, $b):
    }

    private function tiny($a, $b)
    {
    }
}Code language: PHP (php)

publicなbarメソッドをテストすることで、間接的にtinyメソッドもテストしているってことね。

でも、心配性だから、小さいメソッドを直接テストしたいんだよね

確かにちょっとまどろっこしい感じはするわね、カバレッジを確認するより、tinyメソッドを直接テストしたいわね

別クラスのpublicメソッドにする

2つめは「別クラスのpublicメソッドにする」ね

class Foo
{
    public function bar()
    {
        $baz = new Baz();
        $c = $baz->tiny($a, $b);
    }
}

class Baz
{
    public function tiny($a, $b)
    {
    }
}Code language: PHP (php)

うん、たぶん、これが正解な気がするんだけど、

publicメソッドにすると、クラス名、メソッド名、引数、返り値を気軽に変更できなくなるのが、ちょっとイヤかな

そうね、他の人がコードベースを検索して、使ってしまうかもしれないものね

テスト対象の可視性を(やや)上げる

3つめは「テスト対象の可視性を(やや)上げる」よ

Javaにはpackageスコープがあるけど、PHPでは難しいね

privateのままではテストできないから、

class Foo
{
    private function tiny($a, $b)
    {
    }
}Code language: PHP (php)

protectedに置き換えておいて

class Foo
{
    protected function tiny($a, $b)
    {
    }
}Code language: PHP (php)

ユニットテスト用のFooMockクラスを作って、publicメソッドにする方法もあるわよ

class FooMock extends Foo
{
    public function tiny($a, $b)
    {
        return parent::tiny($a, $b);
    }
}Code language: PHP (php)

この方法もいいと思うけど、ちょっと面倒だね

privateのまま、リフレクションでアクセスしてテストを書く

4つめは「privateのまま、リフレクションでアクセスしてテストを書く」だわ

class Foo
{
    private function tiny($a, $b)
    {
    }
}

class FooTest
{
    public function testTiny()
    {
        $foo = new Foo();
        $tiny = new ReflectionMethod(get_class($foo), 'tiny');
        $tiny->setAccessible(true);

        // $c = $foo->tiny($a, $b); と同等
        $c = $tiny->invoke($foo, $a, $b);
    }
}Code language: PHP (php)

tinyメソッドを呼んでいるのか、何をしているのか、よくわからないよ

そうね、リフレクションを使うよりは、FooMockクラスとprotectedメソッドのほうが、自然な気がするわね

リフレクションのメソッド呼び出しは、PhpStormなどのIDEのfind usageで検索できないしね

なぜprivateメソッドはテストしなくてもいいのか?

内部の変数をチェックしてはいけない

ちょっと話はかわるんだけど、

KentBeckからの引用がよくわからないんだけど...

コードがきちんと動いているかどうかを変数を使って確かめたくなるときは、設計を改善する機会であると私は考えている。不安に負けて変数をチェックしてしまえば、改善の機会は失われる。

『テスト駆動開発』 第29章 xUnitのパターン p.226

本を読み返してみたわ

ContractオブジェクトがStatusオブジェクトを保持していて、そのStatusは具体的にはOfferedクラスかRunningクラスのどちらかのインスタンスだとする。実装を想像して次のようなテストを書いてしまいがちだ。

ContractTest.java

Contract contract = new Contract(); // ステータスはOffered
contract.begin(); // ステータスをRunningに変更
assertEquals(Running.class, contract.status.class);Code language: Java (java)

このテストは実装に依存しすぎている。statusの表現が真偽値に変わっても通るようなテストを書きたい。statusがRunningに変わったのなら、開始日を尋ねてみるのはどうだろうか。

// ステータスがOfferedのときは例外が発生する
assertEquals(today, contract.startDate());Code language: Java (java)

クラスの型や(内部のプロパティ)を調べるのは、NG

publicメソッドの返り値を調べるのは、OK、ってことかしら

privateメソッドのテストは内部の実装のテストになりやすい

あとね、次の何がよくないのか、ピンとこないよ

しかし、プライベートメソッドに対するテストは内部の実装に対するテストになってしまうことが多く、そして内部の実装に対するテストはリファクタリングの妨げになりがちです。

実装についてテストしてはいけないってこと?

この文の「実装に対するテスト」がどういうものなのか、よくわからないけど

KentBeckの例のような、クラスの型を調べるようなテストは、リファクタの妨げになると思うわ

でも、型を宣言したり、new Contract()するよね、それも実装だよね?

それはいいとおもうわ。それをテストしないことには何もはじまらないもの。

設計が浮かばないときは、変数をチェックする

もうひとつ、ちょっと気になったのは、

リフレクションを使えばプライベートなメソッドのテストは「できる」のですが、そのテストはやがて実装改善の邪魔になりかねません。

リファクタの途中で、通らないテストがでてきたら、捨てちゃっていいと思うだんけど。

そうね、現実にそぐわない実装やテストは、捨ててしまっていいのよ。

でも、せっかく書いたテストなのにもったいないわよね

だから、テストも長く役に立つように、実装や詳細をテストしないほうがいいっててことね

どの程度の実装をテストしちゃいけないのかな?

「実装」が、クラスや内部プロパティのことだとしても、外部IOのことだとしても、どこかで実装に依存しているわけだしね。

そういうことを意識しつつ、テストを書いたほうがいいわ。テストがないことのほうが問題よ。

KentBeckは「設計が浮かばないときは、変数をチェックし(実装をテストし)、涙を流しながら後日のためにメモを残す」って言っているわ。

まとめ

privateメソッドのテストはどうなったの?

別クラスのpublicメソッドにするか、

protectedメソッドにして、テスト用にextendsしたクラスでpublicメソッドにして

テストを書いたほうがいいと思うわ

でもね、全てのメソッドのユニットテストを書かなければいけない、というルールにはしないほうがいいわ

なぜ?

テストを書くのが面倒だから、メソッドに小分けしないようになるのよ

あー、モンスターメソッドになってしまうんだね

タイトルとURLをコピーしました