我编码了大约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旅程中帮助我。谢谢。