通過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物件。