TDD尝试的最佳实践

  • 本文关键字:最佳 TDD testing tdd
  • 更新时间 :
  • 英文 :


i代码约12年,但我从来没有习惯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
    }

这在逻辑上是正确的吗?我想念什么吗?因为这看起来很大,如此令人费解,所以……错了。为了这些测试,我必须创建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());
    }

我从框架上摆脱了两个班级,但是我仍然觉得有问题。我的逻辑中有更简单的方法吗?在这种情况下,您会使用模拟框架还是实现的类?

你们会完全摆脱测试,还是要坚持我的任何解决方案?或如何使该解决方案更好?

希望您能在我的TDD旅程中帮助我。谢谢。

对于您当前的设计,您的最后一次尝试在逻辑上是正确的,并且接近我认为最佳测试用例。

我建议将模拟变量提取到字段。我还将重新排序测试线以在设置,执行和验证之间有明确的区别。将胸部类型提取到常量也可以使测试更容易理解。

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的主要好处是它为您提供有关设计的反馈。您对测试代码大,复杂和错误的感觉是重要的反馈。将其视为设计压力。测试重构以及设计改进时都可以改善测试。

对于您的代码,我会考虑以下设计问题:

  1. 责任是否正确分配?特别是,知道过去和剩余时间是胸部的重新态度吗?
  2. 设计中是否缺少任何概念?也许每个箱子都有一个锁,并且有一个时间基础锁。
  3. 如果我们在施工时通过了Timetoopen而不是类型,该怎么办?将其视为经过针而不是经过干草堆,在该草堆中尚未找到针头。供参考,请参阅此帖子

有关测试如何提供设计反馈的良好讨论,请参阅史蒂夫·弗里曼(Steve Freeman(和纳特·普莱斯(Nat Pryce(指导的不断增长的面向对象的软件。

有关在C#编写可读测试的良好实践,我推荐Roy Osherove的单元测试艺术。

如图所示,在编写单元测试时需要考虑一些主要点

  1. 单元测试的单独项目。

  2. 一类用于编写一类主要功能的单元测试代码。

  3. 涵盖功能中的条件。
  4. 测试驱动开发(TDD(

如果您真的想了解更多(示例(,请查看此教程

单元测试C# - 最佳实践https://www.youtube.com/watch?v=grf4l3aksrs

最新更新