C# 如何在测试环境中替换配置文件?

C# 如何在测试环境中替换配置文件?,c#,unit-testing,mocking,mstest,C#,Unit Testing,Mocking,Mstest,我正在使用MSTest测试应用程序。测试要求某些特定值(通常不存在)出现在应用程序配置文件中 因此,我需要在测试运行时替换一个包含这些值的知名配置文件,以便System.Configuration.ConfigurationManager指向正确的文件。(即我用先前制作的另一个配置文件代替真实的配置文件) 我可以做所有这些,除了当我的测试执行时,System.Configuration.ConfigurationManager已经读取了配置文件,因此新值被忽略 示例代码: static

我正在使用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_文件”)。在生产代码中,如果在应用程序开始时完成,这种重新路由到另一个配置文件的技术将非常有效)

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

        var dict = (System.Collections.Specialized.NameValueCollection)System.Configuration.ConfigurationManager.GetSection("ScheduledTasks");
原因很明显,尽管生产代码行和测试代码现在指向正确的配置文件,但生产配置文件已经加载到内存中,因此测试配置文件实际上被忽略了

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


TIA

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

public class ConfigurationManagerWrapper : IConfigurationProvider
{
    public NameValueCollection GetScheduledTasksSettings()
    {
        return (NameValueCollection)ConfigurationManager
                  .GetSection("ScheduledTasks");
    }
}
然后使您的sut(测试中的类)依赖于易于模拟的
ICoolConfigurationProvider
抽象(还可以考虑返回比名称-值集合更具体的业务):

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
}
[TestMethod]
公共空间应该做些什么
{       
var configMock=new Mock();
configMock.Setup(c=>c.getScheduledTaskSettings())
.Returns(新名称值集合{{“foo”,“bar});
var sut=new sut(configMock.Object);//注入配置提供程序
sut.练习();
//断言
}

谢谢,在我回到这篇文章之前,我偶然发现了同样的东西。很高兴知道我在正确的轨道上。@NeilHaughton welcome:)单元测试也应该是快速的-这是使用模拟而不是读取文件或进行数据库查询的另一个原因我尽可能地使用模拟,但有时遗留代码会碍手碍脚,使其难以令人满意!
[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
}