我編碼了大約12年,但我從來沒有習慣過TDD。此TDD嘗試的最佳實踐
好吧,事情即將改變,但由於我自己都在學習,所以我希望你們能幫助我。
我發佈了一個非常簡單的胸部類遊戲的例子。 當玩家抓住胸部時,它會記錄獲得的當前時間。 這個胸部需要一些時間才能打開,所以我需要出於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
}
這是合乎邏輯的嗎?我錯過了什麼嗎?因爲這看起來很大,如此錯綜複雜,所以......錯了。爲了進行這些測試,我必須創建兩個額外的課程。
讓我們帶着嘲弄的框架看:
[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());
}
我得到的框架擺脫兩班的,不過,我覺得有什麼不對勁。在我的邏輯中有一種更簡單的方法嗎?在這種情況下,你會使用模擬框架還是實現類?
你們會完全擺脫這些測試嗎?還是堅持我的任何解決方案?或者如何使這個解決方案更好?
希望你能在我的TDD旅程中幫助我。謝謝。