2017-04-25 5 views
2

私は約12年間コードを作成していますが、今回はすべてTDDに慣れていません。このTDD試行のベストプラクティス

さて、物事は変わりつつありますが、私はすべて自分で学習しているので、皆さんが私を助けてくれることを願っています。

私はVERY SIMPLEの胸部クラスのゲームの例を投稿しています。 プレイヤーは胸をつかんで、取得した現在の時刻を登録します。 この胸にはオープンに少し時間がかかります。UIの理由から、オープンに必要な残り時間を表示する必要があります。 各胸にはタイプがあり、このタイプはデータベースのオープンに要する時間の値にバインドされています。

これは、「テストを行わないだけのことを成し遂げた、速い考え方」です。 ChestsDatabaseとDateManagerは、データベースにバインドされた値と現在のシステム時刻がクラスにラップされたシングルトンであると考えてください。もちろん

public class Chest { 
    private readonly int _type; 
    private readonly float _timeObtained; 

    public Chest(int type, float timeObtained) { 
     _type = type; 
     _timeObtained = timeObtained; 
    } 

    public bool IsOpened() { 
     return GetRemainingTime() <= 0; 
    } 

    // It depends heavily on this concrete Singleton class 
    public float GetRemainingTime() { 
     return ChestsDatabase.Instance.GetTimeToOpen(_type) - GetPassedTime(); 
    } 

    // It depends heavily on this concrete Singleton class 
    private float GetPassedTime() { 
     return DateManager.Instance.GetCurrentTime() - _timeObtained; 
    } 
} 

、私は依存性注入の方法でそれを作ったとシングルトンを取り除くことができた:私は、同じロジックを表現するためのインターフェースを使用する場合

public class Chest { 
    private readonly ChestsDatabase _chestsDatabase; 
    private readonly DateManager _dateManager; 
    private readonly int _type; 
    private readonly float _timeObtained; 

    public Chest(ChestsDatabase chestsDatabase, DateManager dateManager, int type, float timeObtained) { 
     _chestsDatabase = chestsDatabase; 
     _dateManager = dateManager; 
     _type = type; 
     _timeObtained = timeObtained; 
    } 

    public bool IsOpened() { 
     return GetRemainingTime() <= 0; 
    } 

    public float GetRemainingTime() { 
     return _chestsDatabase.GetTimeToOpen(_type) - GetPassedTime(); 
    } 

    private float GetPassedTime() { 
     return _dateManager.GetCurrentTime() - _timeObtained; 
    } 
} 

何?これははるかに "TDDフレンドリー"になるでしょうか? (私が最初にテストを行ったと仮定して)

public class Chest { 
    private readonly IChestsDatabase _chestsDatabase; 
    private readonly IDateManager _dateManager; 
    private readonly int _type; 
    private readonly float _timeObtained; 

    public Chest(IChestsDatabase chestsDatabase, IDateManager dateManager, int type, float timeObtained) { 
     _chestsDatabase = chestsDatabase; 
     _dateManager = dateManager; 
     _type = type; 
     _timeObtained = timeObtained; 
    } 

    public bool IsOpened() { 
     return GetRemainingTime() <= 0; 
    } 

    public float GetRemainingTime() { 
     return _chestsDatabase.GetTimeToOpen(_type) - GetPassedTime(); 
    } 

    private float GetPassedTime() { 
     return _dateManager.GetCurrentTime() - _timeObtained; 
    } 
} 

しかし、どうしてそんなことをテストするのですか? このようになりますか?

[Test] 
    public void SomeTimeHavePassedAndReturnsRightValue() 
    { 
     var mockDatabase = new MockChestDatabase(); 
     mockDatabase.ForType(0, 5); // if Type is 0, then takes 5 seconds to open 
     var mockManager = new MockDateManager(); 
     var chest = new Chest(mockDatabase, mockManager, 0, 6); // Got a type 0 chest at second 6 
     mockManager.SetCurrentTime(8); // Now it is second 8 
     Assert.AreEqual(3, chest.GetRemainingTime()); // Got the chest at second 6, now it is second 8, so it passed 2 seconds. We need 5 seconds to open this chest, so the remainingTime is 3 
    } 

これは論理的に正しいですか?何か不足していますか?これは非常に大きく、畳み込まれているので、間違っているようです。私はこれらのテストの目的のために〜2つの余分なクラスを作成しなければなりませんでした。

はのはモックフレームワークを見てみましょう:

[Test] 
    public void SomeTimeHavePassedAndReturnsRightValue() 
    { 
     var mockDatabase = Substitute.For<IChestsDatabase>(); 
     mockDatabase.GetTimeToOpen(0).Returns(5); 
     var mockManager = Substitute.For<IDateManager>(); 
     var chest = new Chest(mockDatabase, mockManager, 0, 6); 
     mockManager.GetCurrentTime().Returns(8); 
     Assert.AreEqual(3, chest.GetRemainingTime()); 
    } 

私はフレームワークを持つ2つのクラスを処分したが、それでも、私は間違って何かがあると感じています。私の論理にはもっと単純な方法がありますか?この単一のケースでは、模擬フレームワークや実装クラスを使用しますか?

皆さんはテストを一掃してもらえませんか、私のソリューションのいずれかを主張しますか?またはこのソリューションをより良くする方法は?

私のTDD旅で私を助けてくれることを願っています。ありがとう。

答えて

3

を現在の設計では、最後の試行は論理的に正しいものであり、最適なテストケースと考えられるものに近いです。

モック変数をフィールドに抽出することをおすすめします。また、セットアップ、実行、検証を明確に区別するためにテスト行を並べ替えることにします。胸のタイプを一定に抽出することで、テストが分かりやすくなります。

private IChestsDatabase mockDatabase = Substitute.For<IChestsDatabase>(); 
private IDateManager mockManager = Substitute.For<IDateManager>(); 
private const int DefaultChestType = 0; 

[Test] 
public void RemainingTimeIsTimeToOpenMinusTimeAlreadyPassed() 
{ 
    mockDatabase.GetTimeToOpen(DefaultChestType).Returns(5); 
    mockManager.GetCurrentTime().Returns(6+2); 
    var chest = new Chest(mockDatabase, mockManager, DefaultChestType, 6); 

    var remainingTime = chest.GetRemainingTime(); 

    Assert.AreEqual(5-2, remainingTime); 
} 

さらに一般的なコメントです。 TDDの主な利点は、デザインに対するフィードバックを提供することです。テストコードが大きく畳み込まれていて間違っているという気持ちは重要なフィードバックです。 design pressureと考えてください。テストは、テストのリファクタリングとデザインの改善の両方で改善されます。あなたのコードの場合

、私はこれらの設計問題を検討する:

  1. は適切に割り当てられた責任はありますか?特に、渡された時間と残りの時間を知ることは、胸の許容度ですか?
  2. デザインに欠けている概念はありますか?たぶん、各胸にロックがあり、タイムベースのロックがあります。
  3. 建設時にType to Chestの代わりにTimeToOpenを渡すとどうなりますか?針がまだ見つからないような乾草を渡す代わりに、針を渡すと考えてください。参考までに、テストはスティーブ・フリーマンとNATプライスによってテストに導かれて成長するオブジェクト指向のソフトウェアを参照して、設計のフィードバックを提供することができる方法の良い議論についてthis post

を参照してください。

C#で読みやすいテストを書くための優れたプラクティスセットについては、Roy OsheroveのThe Unit Testing of Unit Testingをお勧めします。

2

ユニットテストのための

  1. 独立したプロジェクトを示すように、ユニットテストを書きながら検討する必要があるいくつかの主要なポイントがあります。

  2. メインクラス コードの1つのクラスの関数の単体テストを書くための1つのクラス。

  3. 機能内のカバー条件。
  4. テスト駆動開発(TDD)

あなたが本当に(例付き)詳細をお知りになりたい場合は、このチュートリアルでは#C

ユニットテストを見て - あなたのためのベストプラクティスhttps://www.youtube.com/watch?v=grf4L3AKSrs

関連する問題