【问题标题】:How to substitute config file in test environment?如何在测试环境中替换配置文件?
【发布时间】:2014-01-21 14:36:13
【问题描述】:

我正在使用 MSTest 测试应用程序。测试需要某些特定值(通常不存在)出现在应用程序配置文件中。

所以我需要在测试运行时替换一个包含值的知名配置文件,以便 System.Configuration.ConfigurationManager 指向正确的文件。 (即,我通过替换我之前制作的另一个文件来伪造真实的配置文件)

我可以做所有这些,除了在我的测试执行时,System.Configuration.ConfigurationManager 已经读取了配置文件,因此新值将被忽略。

示例代码:

    static TemporaryConfigFile config;
    [ClassInitialize]
    public static void ClassInitialise(TestContext testContext)
    {
        string sourceResource = "Intra_Matrix_Scheduler_Tests.Resources.test.config";
        string tempConfigFileName = "test.config";
        config = TemporaryConfigFile.CreateFromEmbeddedResource(Assembly.GetExecutingAssembly(), sourceResource, tempConfigFileName);
    }

    [ClassCleanup]
    public static void ClassCleanUp()
    {
        config.Dispose();
    }

(上面的代码创建了一个具有已知测试值的新配置文件,并将 AppDomain.CurrentDomain("APP_CONFIG_FILE") 指向它。在生产代码中,如果在开始时完成,这种重新路由到另一个配置文件的技术非常有效应用程序)

问题在于以下生产线在进行测试时,无法检索到所需的测试值:

        var dict = (System.Collections.Specialized.NameValueCollection)System.Configuration.ConfigurationManager.GetSection("ScheduledTasks");

原因很明显,虽然生产代码行和测试代码现在指向正确的配置文件,但生产配置文件已经加载到内存中,因此测试配置文件实际上被忽略了。

所以问题是:如何强制 System.Configuration.ConfigurationManager 重新读取配置文件,或者如何伪造配置文件?或者,如何在测试期间直接修改内存配置文件? (AFAIK 我不能使用依赖注入和 MOQ 来模拟它,因为 System.Configuration.ConfigurationManager 是静态的)

TIA

【问题讨论】:

    标签: c# unit-testing mocking mstest


    【解决方案1】:

    我建议您将类与其他真实类(如ConfigurationManager)隔离开来,尤其是与环境(文件、网络、数据库等)隔离开来,因为您的测试可能由于与您的代码无关的外部原因而失败正在测试(文件可能不存在、错误的数据库连接等)。如果您要创建自己的非静态配置管理器,这很容易做到,它将所有工作委托给ConfigurationManager

    public class ConfigurationManagerWrapper : IConfigurationProvider
    {
        public NameValueCollection GetScheduledTasksSettings()
        {
            return (NameValueCollection)ConfigurationManager
                      .GetSection("ScheduledTasks");
        }
    }
    

    然后让你的 sut(被测类)依赖于易于模拟的 ICoolConfigurationProvider 抽象(还考虑返回比名称-值集合更具体的业务):

    public interface IConfigurationProvider
    {
         NameValueCollection GetScheduledTasksSettings();
    }
    

    而 sut 看起来像:

    public class SUT
    {
        private IConfigurationProvider _configProvider;
    
        public SUT(IConfigurationProvider configProvider)
        {
            _configProvider = configProvider;
        }
    
        public void Exercise()
        {
            var dict = _configProvider.GetScheduledTasksSettings();
            // ...
        }
    }
    

    现在您可以轻松地为您的测试提供任何值:

    [TestMethod]
    public void ShouldDoSomething()
    {       
       var configMock = new Mock<IConfigurationProvider>();
    
       configMock.Setup(c => c.GetScheduledTasksSettings())
                 .Returns(new NameValueCollection {{ "foo", "bar" }});
    
       var sut = new SUT(configMock.Object); // inject configuration provider
       sut.Exercise();
    
       // Assertions
    }
    

    【讨论】:

    • 谢谢,在我回到这个线程之前,我偶然发现了一些类似的东西。很高兴知道我在正确的轨道上。
    • @NeilHaughton 欢迎 :) 单元测试也应该很快 - 这是使用模拟而不是读取文件或进行数据库查询的另一个原因
    • 我尽我所能使用模拟,但有时遗留代码会妨碍它,让人难以舒适!
    猜你喜欢
    • 1970-01-01
    • 2018-06-05
    • 1970-01-01
    • 2011-08-31
    • 2018-04-14
    • 2015-08-31
    • 2019-10-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多