単体テスト中にのみ使用されるメソッドを導入しても大丈夫ですか?


12

最近、ファクトリーメソッドをTDDしていました。その方法は、単純なオブジェクト、またはデコレータでラップされたオブジェクトを作成することでした。装飾されたオブジェクトは、いずれもStrategyClassを拡張するいくつかのタイプのいずれかです。

私のテストでは、返されたオブジェクトのクラスが期待どおりかどうかを確認したかった。単純なオブジェクトosが返されたときは簡単ですが、デコレータでラップされたらどうしますか?

ext/Reflectionラップされたオブジェクトのクラスを見つけるために使用できるようにPHPでコーディングしますが、物事を複雑にしすぎているようで、TDDのルールにやや反対しています。

代わりにgetClassName()、StrategyClassから呼び出されたときにオブジェクトのクラス名を返すように導入することにしました。ただし、デコレータから呼び出された場合は、装飾されたオブジェクトの同じメソッドによって返された値を返します。

より明確にするためのいくつかのコード:

interface StrategyInterface {
  public function getClassName();
}

abstract class StrategyClass implements StrategyInterface {
  public function getClassName() {
    return \get_class($this);
  }
}

abstract class StrategyDecorator implements StrategyInterface {

  private $decorated;

  public function __construct(StrategyClass $decorated) {
    $this->decorated = $decorated;
  }

  public function getClassName() {
    return $this->decorated->getClassName();
  }

}

そして、PHPUnitテスト

 /**
   * @dataProvider providerForTestGetStrategy
   * @param array $arguments
   * @param string $expected
   */
  public function testGetStrategy($arguments, $expected) {

    $this->assertEquals(
      __NAMESPACE__.'\\'.$expected,  
      $this->object->getStrategy($arguments)->getClassName()
    )
  }

//below there's another test to check if proper decorator is being used

ここでの私のポイントは次のとおりです。ユニットテストを簡単にする以外に用途のないそのようなメソッドを導入しても大丈夫ですか?どういうわけかそれは私には正しく感じられません。


おそらく、この質問はあなたの質問、programmers.stackexchange.com / questions / 231237 /… についてより多くの洞察を投げかけるでしょう。 。
クレメントマークAABA

回答:


13

私の考えは何である、あなたは何もしないべきではないだけで、それがテスト容易化のために必要とされているので。人々が下す多くの決定がテスト容易性に利益をもたらし、テスト容易性が主要な利点でさえあるかもしれませんが、それは他のメリットについての良い設計決定であるべきです。これは、いくつかの望ましいプロパティがテスト可能でないことを意味します。別の例は、あるルーチンがどれほど効率的かを知る必要がある場合です。例えば、ハッシュマップはハッシュ値の均等に分散された範囲を使用します-外部インターフェースには何も表示されません。

「適切な戦略クラスを取得していますか」と考える代わりに、「取得したクラスは、この仕様がテストしようとしていることを実行していますか?」と考えます。内部の配管をテストできるのに便利ですが、その必要はありません。ノブを回して、お湯が出るか冷えるかを確認するだけです。


+1 OPは、TDD機能テストではなく、「クリアボックス」単体テストについて説明しています
スティーブンA.ロウ

1
ファクトリーメソッドがその仕事をしているかどうかをテストしたいとき、StrategyClassアルゴリズムのテストを追加することに少し消極的ですが、私はその点を見ます。この種の分離私見を破る。私がそれを避けたいもう一つの理由は、これらの特定のクラスがデータベースで動作するため、追加のモック/スタブが必要になることです。
-Mchl

一方、この質問に照らして、programmers.stackexchange.com / questions /
86656 /…

メソッドを追加すると、それらはユーザーとの契約の一部になります。最終的には、テスト専用の関数を呼び出し、結果に基づいて分岐するコーダーになります。一般的に、私はできるだけ少ないクラスを公開することを好みます。
-BillThor

5

これについての私の見解は-時には、ソースコードをテストしやすくするために少し修正しなければならない場合があります。これは理想的ではなく、テストにのみ使用することになっている関数でインターフェースを乱雑にする言い訳にはならないので、ここでは一般的に節度が重要です。また、コードのユーザーがオブジェクトとの通常のやり取りのためにテストインターフェイス関数を突然使用するような状況になりたくないでしょう。

私がこれを処理するための好ましい方法(そして、私は主にCスタイルの言語でコーディングしているため、PHPでこれを行う方法を示すことができないことを謝罪する必要があります)は、「テスト」関数をオブジェクト自体によって外部に公開されますが、派生オブジェクトからアクセスできます。テストの目的で、実際にテストしたいオブジェクトとのやり取りを処理するクラスを派生させ、ユニットテストでその特定のオブジェクトを使用します。C ++の例は次のようになります。

生産タイプクラス:

class ProdObj
{
  ...
  protected:
     virtual bool checkCertainState();
};

テストヘルパークラス:

class TestHelperProdObj : public ProdObj
{
  ...
  public:
     virtual bool checkCertainState() { return ProdObj::checkCertainState(); }
};

そうすれば、少なくとも、メインオブジェクトで「テスト」タイプの関数を公開する必要はありません。


興味深いアプローチ。私はこれをどのように適応させることができるかを見る必要があります
Mchl

3

数ヶ月前、新しく購入した食器洗い機を置いたときにホースから大量の水が出てきましたが、これはおそらくそれが来た工場で適切にテストされたという事実によるものです。組立ラインでのテストのみを目的として、そこにある機械に取り付け穴や物を見るのは珍しいことではありません。

テストは重要です。必要な場合は、何かを追加してください。

しかし、いくつかの代替案を試してください。リフレクションベースのオプションはそれほど悪くはありません。必要なものに保護された仮想アクセサーを用意し、テストおよびアサートする派生クラスを作成できます。おそらく、クラスを分割して、結果のリーフクラスを直接テストできます。ソースコード内のコンパイラ変数を使用してテストメソッドを非表示にすることもオプションです(PHPについてほとんど知らないので、PHPでそれが可能かどうかはわかりません)。

あなたのコンテキストでは、デコレータ内の適切なコンポジションをテストせずに、デコレーションに期待される動作をテストすることを決定できます。これにより、おそらく技術仕様ではなく、予想されるシステムの動作にいくらか焦点を当てることになります(デコレーターパターンは、機能的な観点から何を購入しますか?)。


1

私は絶対的なTDD初心者ですが、追加する方法に依存しているようです。私がTDDについて理解していることから、あなたのテストはAPIの作成をある程度「駆動」することになっています。

大丈夫なとき:

  • メソッドがカプセル化を壊さず、オブジェクトの責任に沿った目的を果たす限り。

うまくいかない場合:

  • メソッドが決して役に立たないようなものであるか、他のインターフェイスメソッドとの関係で意味をなさないように思われる場合、それはまとまりのない、または紛らわしいかもしれません。その時点で、それはそのオブジェクトの私の理解を濁してしまうでしょう。
  • ジェレミーの例、「...あるルーチンがどれほど効率的かを知る必要があるとき、たとえば、ハッシュマップはハッシュ値の均等に分散された範囲を使用します-外部インターフェイスには何も表示されません」
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.