ASP.NET 系列:單元測試之ConfigurationManager

剛哥521發表於2016-02-02

通過ConfigurationManager使用.NET配置檔案時,可以通過新增配置檔案進行單元測試,雖然可以通過測試但達不到解耦的目的。使用IConfigurationManager和ConfigurationManagerWrapper對ConfigurationManager進行適配是更好的方式,ConfigurationManagerWrapper提供.NET配置檔案方式的實現,如果需要支援其他配置,建立IConfigurationManager介面的不同的實現類即可。

1.定義IConfigurationManager介面

原本依賴ConfigurationManager的程式碼現在依賴IConfigurationManager。可以在單元測試時方便的Mock。

public interface IConfigurationManager
{
    NameValueCollection AppSettings { get; }
    ConnectionStringSettingsCollection ConnectionStrings { get; }
    object GetSection(string sectionName);
}

2.建立適配類ConfigurationManagerWrapper

非單元測試環境使用ConfigurationManagerWrapper作為IConfigurationManager的預設實現。

public class ConfigurationManagerWrapper : IConfigurationManager
{
    public NameValueCollection AppSettings
    {
        get
        {
            return ConfigurationManager.AppSettings;
        }
    }

    public ConnectionStringSettingsCollection ConnectionStrings
    {
        get
        {
            return ConfigurationManager.ConnectionStrings;
        }
    }

    public object GetSection(string sectionName)
    {
        return ConfigurationManager.GetSection(sectionName);
    }
}

3.自定義泛型配置介面

在我們的程式碼需要使用配置時,可以考慮建立通用的泛型介面也可以使用專用的強型別的介面。這裡演示使用通用的介面。

public interface IConfiguration
{
    T Get<T>(string key, T @default);
}

4.實現泛型介面配置介面的.NET配置檔案版本

AppConfigAdapter直接不使用ConfigurationManager而是依賴IConfigurationManager介面。

public class AppConfigAdapter : IConfiguration
{
    private IConfigurationManager _configurationManager;

    public AppConfigAdapter(IConfigurationManager configurationManager)
    {
        this._configurationManager = configurationManager;
    }

    public T Get<T>(string nodeName, T @default)
    {
        var value = this._configurationManager.AppSettings[nodeName];
        return value == null ? @default : (T)Convert.ChangeType(value, typeof(T));
    }
}

5.對泛型配置介面的實現進行單元測試

使用最流行的單元測試框架和Mock類庫:xUnit+Moq進行單元測試。

public class AppConfigAdapterTest
{
    [Fact]
    public void GetStringTest()
    {
        var key = "key";
        var value = "value";
        var configuration = new AppConfigAdapter(this.GetConfigurationManager(o => o.Add(key, value.ToString())));
        Assert.Equal(configuration.Get(key, string.Empty), value);
    }

    [Fact]
    public void GetIntTest()
    {
        var key = "key";
        var value = 1;
        var configuration = new AppConfigAdapter(this.GetConfigurationManager(o => o.Add(key, value.ToString())));
        Assert.Equal(configuration.Get(key, int.MinValue), value);
    }

    [Fact]
    public void GetBoolTest()
    {
        var key = "key";
        var value = true;
        var configuration = new AppConfigAdapter(this.GetConfigurationManager(o => o.Add(key, value.ToString())));
        Assert.Equal(configuration.Get(key, false), value);
    }

    [Fact]
    public void GetDateTimeTest()
    {
        var key = "key";
        var value = DateTime.Parse(DateTime.Now.ToString());
        var configuration = new AppConfigAdapter(this.GetConfigurationManager(o => o.Add(key, value.ToString())));
        Assert.Equal(configuration.Get(key, DateTime.MinValue), value);
    }

    [Fact]
    public void GetDecimalTest()
    {
        var key = "key";
        var value = 1.1m;
        var configuration = new AppConfigAdapter(this.GetConfigurationManager(o => o.Add(key, value.ToString())));
        Assert.Equal(configuration.Get(key, decimal.MinValue), value);
    }

    private IConfigurationManager GetConfigurationManager(Action<NameValueCollection> set)
    {
        var appSettings = new NameValueCollection();
        set(appSettings);
        var configurationManager = new Mock<IConfigurationManager>();
        configurationManager.Setup(o => o.AppSettings).Returns(appSettings);
        return configurationManager.Object;
    }
}

執行結果:

6.總結

使依賴ConfigurationManager靜態類的程式碼轉換為依賴IConfigurationManager介面,執行時注入ConfigurationManagerWrapper實現類。單元測試時使用Mock模擬IConfigurationManager物件。

相關文章