如何使单件设计可测试?

时间:2013-12-06 14:05:38

标签: c# unit-testing singleton

假设我有以下单身人士:

public class ConfigReader
{
    static readonly ConfigReader instance = new ConfigReader();

    private ConfigReader() { }

    public static ConfigReader Instance { get { return instance; } }

    public int RecordsPerPage
    {
        get 
        { 
            var configPath = DB.Table<ConfigSource>().GetFieldValue("Path").ToString();
            if (configSource == "Database")
                return Convert.ToInt32(DB.Table<Configuration>().GetFieldValue("RecordsPerPage"));
            else
                return Convert.ToInt32(ConfigurationManager.AppSettings["RecordsPerPage"]);
        }
    }
}

如何编写单元测试来模拟DB.Table&amp; ConfigurationManager.AppSettings在这个单例中调用,以便我可以用单元测试来测试它吗?

实际上这导致了TDD的另一个问题:我们在设计系统时是否真的需要考虑可测试性?似乎并非每个设计都符合TDD原则,如DI等。我是否误解了某些内容?

1 个答案:

答案 0 :(得分:2)

在设计系统时我们应该考虑可测试性吗?如果您编写符合SOLID原则的代码,那么您的系统将是可测试的。因此,只需考虑是否需要良好的面向对象设计。

替代解决方案 - 如果您使用的是依赖注入框架,那么您只需关注此类的单一责任 - 读取配置值。第二个责任(保持单个类的实例)将转到DI框架,你可以在这里使用简单的构造函数注入。

测试你的单例类 - 为了模拟类依赖,你应该依赖于抽象而不是实现。您应该通过依赖注入提供实现。这里有几个选项 - 您可以将RecordsPerPage属性转换为方法并使用参数注入:

public int RecordsPerPage(IConfigSource source)
{
     var configPath = source.GetFieldValue("Path").ToString();
     if (configPath == "Database")
          return Convert.ToInt32(source.GetFieldValue("RecordsPerPage"));

     return Convert.ToInt32(ConfigurationManager.AppSettings["RecordsPerPage"]);
}

现在你可以将IConfigSource的模拟传递给你的单身人士(Moq样本):

int expected = random.Next();
Mock<IConfigSource> sourceMock = new Mock<IConfigSource>();
sourceMock.Setup(s => s.GetFieldValue("Path")).Returns("Database");
sourceMock.Setup(s => s.GetFieldValue("RecordsPerPage")).Returns(expected);

var reader = ConfigReader.Instance;
var actual = reader.RecordsPerPage(sourceMock.Object);

Assert.That(actual, Is.EqualTo(expected));
sourceMock.VerifyAll();

如果你想重用IConfigSource来调用不同的单例,那么你可以声明属性来设置它(属性注入):

public IConfigSource Source { get; set; }

在测试期间设置源:

var reader = ConfigReader.Instance;
reader.Source = sourceMock.Object;
var actual = reader.RecordsPerPage;