单例如何妨碍可测试性

How can singleton hamper testability

我读到单例不利于可测试性。你能详细说明一下吗?单身狗就不能被嘲笑吗?他们写是由于紧耦合,但我不明白为什么。

这取决于您如何使用单例以及该单例是否实现了接口。如果它是单例的事实是 实现细节 ,那么这可能没问题。如果您在要测试的代码中使用它的单例特性,那就更成问题了。

以我的Noda Time项目为例。它有一个 IClock 接口代表 "a way of getting the current time"。它有一个名为 SystemClock 的实现,它是一个单例。现在考虑一些使用它的代码:

public class BadClass
{
    public bool IsIt2016Yet()
    {
        var now = SystemClock.Instance.Now;
        return now.InUtc().Year >= 2016;
    }
}

如果不以某种方式更改 SystemClock.Instance 的功能,我们就无法测试该代码。该代码 与单例 class 紧密耦合 。但是,考虑一下:

public class GoodClass
{
    private readonly IClock clock;

    public GoodClass(IClock clock)
    {
        this.clock = clock;
    }

    public bool IsIt2016Yet()
    {
        var now = clock.Now;
        return now.InUtc().Year >= 2016;
    }
}

现在你很好 - 在你的生产代码中你设置了依赖注入以在任何你想要 IClock 的地方使用 SystemClock.Instance 但在测试代码中你可以使用一个假时钟 - 例如NodaTime.Testing 包中提供的那个。在生产中,代码最终将使用单例,但它并没有紧密耦合。

当然,这是假设您已适当设置依赖项注入。如果您希望能够在任何地方构造一个实例而不关心它需要一个时钟这一事实,您可以选择一个选项,可选地 将 class 耦合到SystemClock 单例:

public GoodClass(IClock clock)
{
    this.clock = clock;
}

public GoodClass() : this(SystemClock.Instance)
{
}

现在它仍然是可测试的(并且通常是灵活的),因为您 可以 传递一个时钟,但它很方便,因为它有效地 "defaults" 到系统时钟。 (您可以使用可选参数执行此操作,并使 null 的参数最终使用 SystemClock.Instance 作为另一种选择。)