メソッドがTDDで再設計されてプライベートになった場合、メソッドのテストはどうなりますか?


29

他のキャラクターやそのようなものを攻撃するキャラクターでロールゲームの開発を始めたとしましょう。

TDDを適用して、Character.receiveAttack(Int)メソッド内のロジックをテストするテストケースを作成します。このようなもの:

@Test
fun healthIsReducedWhenCharacterIsAttacked() {
    val c = Character(100) //arg is the health
    c.receiveAttack(50) //arg is the suffered attack damage
    assertThat(c.health, is(50));
}

メソッドをテストreceiveAttackする10のメソッドがあるとします。次に、メソッドCharacter.attack(Character)(メソッドを呼び出すreceiveAttack)を追加し、TDDサイクルでテストした後、決定するCharacter.receiveAttack(Int)必要がありますprivate

以前の10個のテストケースはどうなりますか?それらを削除する必要がありますか?メソッドを保持する必要publicがあります(そうは思わない)。

この質問は、プライベートメソッドをテストする方法ではなく、TDDを適用する際の再設計後にそれらを処理する方法に関するものです。



10
プライベートの場合はテストしませんが、簡単です。リファクタリングダンスを
-kayess

6
私はおそらくここで穀物に反対しています。しかし、私は通常、プライベートメソッドを一切避けています。少ないテストよりも多くのテストを好みます。人々が何を考えているのかを知っています。「それで、消費者に公開したくない機能はありませんか?」はい、公開したくないたくさんあります。代わりに、プライベートメソッドがある場合、独自のクラスにリファクタリングし、元のクラスのクラスを使用します。新しいクラスは、internalまたはそれと同等の言語としてマークして、公開されないようにすることができます。実際、Kevin Clineの答えはこの種のアプローチです。
user9993

3
@ user9993後​​方にあるようです。より多くのテストを行うことが重要な場合、重要なことを見逃していないことを確認する唯一の方法は、カバレッジ分析を実行することです。また、カバレッジツールの場合、メソッドがプライベートまたはパブリック、またはその他の方法であるかどうかはまったく関係ありません。あるものを公開することでカバレッジ分析の欠如を何らかの形で補うことで、私は恐れているという誤った安心感を与えることを願っています
-gnat

2
@gnatしかし、「カバレッジがない」ことについては何も言いませんでしたか?「少ないテストよりも多くのテストを好む」についての私のコメントは、それを明らかにしたはずです。あなたが正確に何を取得しているかはわかりませんが、もちろん、抽出したコードもテストします。それが全体のポイントです。
user9993

回答:


52

TDDでは、テストは設計の実行可能なドキュメントとして機能します。デザインが変更されたので、明らかにドキュメントも必要です!

TDDでは、attackメソッドが表示される唯一の方法は、失敗したテストパスの結果であることに注意してください。つまり、attack他のテストによってテストされています。これは、間接的に のテストでreceiveAttackカバーされることを意味しattackます。理想的には、への変更receiveAttackattackのテストの少なくとも1つに違反するはずです。

そして、もしそうでなければ、receiveAttackその機能はもはや必要ではなく、もはや存在すべきではありません!

ですから、receiveAttackは既にでテストされているattackため、テストを保持するかどうかは関係ありません。場合は、あなたのテストフレームワークを作ることは簡単でプライベートメソッドをテストするために、そして場合あなたはプライベートメソッドをテストすることを決定し、その後、あなたはそれらを維持することができます。ただし、テストの範囲と信頼性を失うことなく削除することもできます。


14
「テストフレームワークによってプライベートメソッドを簡単にテストでき、プライベートメソッドをテストすることにした場合は、それらを保持できます」を除いて、これは良い答えです。プライベートメソッドは実装の詳細であり、直接テストしないでください。
デビッドアルノ

20
@DavidArno:私は同意しません。その理由により、モジュールの内部は決してテストされるべきではないということです。ただし、モジュールの内部は非常に複雑になる可能性があるため、個々の内部機能ごとに単体テストを行うことは有益です。単体テストは、機能の一部の不変条件をチェックするために使用されます。プライベートメソッドに不変条件(事前条件/事後条件)がある場合、単体テストは有用です。
マチューM.17年

8
その理由により、モジュールの内部をテストすべきではありません」。これらの内部は直接テストしないでください。すべてのテストは、パブリックAPIのみをテストする必要があります。内部要素がパブリックAPIを介して到達できない場合、何もしないので削除します。
デビッドアルノ

28
@DavidArnoそのロジックにより、(ライブラリではなく)実行可能ファイルをビルドしている場合、ユニットテストはまったくないはずです。-「関数呼び出しはパブリックAPIの一部ではありません!コマンドライン引数のみです!コマンドライン引数を介してプログラムの内部関数に到達できない場合、何もしないので削除してください。」-プライベート関数はクラスのパブリックAPIの一部ではありませんが、クラスの内部APIの一部です。また、クラスの内部APIを必ずしもテストする必要はありませんが、実行可能ファイルの内部APIをテストするために同じロジックを使用することもできます。
RM

7
@RM、非モジュール方式で実行可能ファイルを作成する場合、内部の脆弱なテストを選択するか、実行可能ファイルとランタイムI / Oを使用した統合テストのみを使用する必要があります。したがって、ストローマンバージョンではなく、実際のロジックによって、モジュール形式で作成します(たとえば、ライブラリのセットを使用して)。その後、これらのモジュールのパブリックAPIを非脆弱な方法でテストできます。
デビッドアルノ

23

メソッドがテストを必要とするほど複雑な場合は、何らかのクラスでパブリックにする必要があります。だからあなたはからリファクタリングします:

public class X {
  private int complexity(...) {
    ...
  }
  public void somethingElse() {
    int c = complexity(...);
  }
}

に:

public class Complexity {
  public int calculate(...) {
    ...
  }
}

public class X {
  private Complexity complexity;
  public X(Complexity complexity) { // dependency injection happiness
    this.complexity = complexity;
  }

  public void something() {
    int c = complexity.calculate(...);
  }
}

X.complexityの現在のテストをComplexityTestに移動します。次に、複雑さをあざけることによってX.somethingにテキストを送信します。

私の経験では、より小さなクラスとより短いメソッドにリファクタリングすることは大きな利点をもたらします。理解しやすく、テストしやすく、最終的には予想以上に再利用されます。


あなたの答えは、OPの質問に対する私のコメントで私が説明しようとしていたアイデアをより明確に説明しています。いい答えだ。
user9993

3
ご回答有難うございます。実際、receiveAttackメソッドは非常に単純です(this.health = this.health - attackDamage)。おそらく、別のクラスにそれを抽出することは、この時点では過剰に設計されたソリューションです。
ヘクター

1
これは間違いなくOPにとってはやり過ぎです-彼は月に飛ぶのではなく、店に行きたいのですが、一般的な場合には良い解決策です。

関数がそんなに単純な場合、最初から関数として定義されていることもあります。
デビッドK

1
今日はやり過ぎかもしれませんが、このコードに大幅な変更が加えられた6か月後には、メリットが明らかになります。そして最近のIDEでは、確かにいくつかのコードを別のクラスに抽出することは、実行時のバイナリではすべて同じになることを考慮して、せいぜいオーバーエンジニアリングされたソリューションであるはずです。
スティーブンバーン

6

receiveAttackメソッドをテストする10のメソッドがあるとします。次に、Character.attack(Character)メソッド(receiveAttackメソッドを呼び出す)を追加し、TDDサイクルでテストした後、Character.receiveAttack(Int)をプライベートにする必要があります。

ここで心に留めておくべきことは、あなたが下している決定は、APIからメソッドを削除することであるということです。後方互換性のおかげで、

  1. 削除する必要がない場合は、APIに残します
  2. 削除する必要がない場合 まだ、非推奨としてマークし、可能であれば、寿命の終わりが来たときに文書化してください
  3. 削除する必要がある場合は、メジャーバージョンが変更されています。

APIがメソッドをサポートしなくなると、テストは削除または置換されます。その時点で、privateメソッドは実装の詳細であり、リファクタリングできるようにする必要があります。

その時点で、テストスイートが純粋にパブリックAPIを介して対話するのではなく、実装に直接アクセスする必要があるかどうかという標準的な質問に戻ります。プライベートメソッドは、テストスイートが邪魔になることなく置き換えることができるものです。そのため、それに結合するテストがなくなることを期待します-廃止されるか、実装とともに個別にテスト可能なコンポーネントに移行します。


3
廃止は必ずしも懸念事項ではありません。「開発を始めましょう...」という質問から、ソフトウェアがまだリリースされていない場合、非推奨は問題ではありません。さらに、「ロールゲーム」、これが再利用可能なライブラリではなく、エンドユーザー向けのバイナリソフトウェアであることを意味します。一部のエンドユーザーソフトウェアにはパブリックAPI(MS Officeなど)がありますが、ほとんどはありません。でもソフトウェア行い、公開APIを持っているだけで、それの一部は(例えばLUA拡張子を持つゲーム)、または他の機能をスクリプト、プラグインのために露出しています。それでも、OPが記述する一般的なケースのアイデアを持ち出す価値はあります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.