无法使用 ConfigurationManager 在 C# .NET Core 单元测试项目中读取 app.config

Posted

技术标签:

【中文标题】无法使用 ConfigurationManager 在 C# .NET Core 单元测试项目中读取 app.config【英文标题】:Can't read app.config in C# .NET Core unit test project with ConfigurationManager 【发布时间】:2018-07-17 22:03:06 【问题描述】:

我创建了一个简单的单元测试项目来读取 app.config 文件。目标框架是 Core 2.0。我还创建了一个 Core 2.0 控制台应用程序,对自己进行健全性检查,以确保我没有做任何奇怪的事情(与 .NET 4.6.1 单元测试项目中预期的相同的测试通过了)。

控制台应用程序可以正常读取 app.config,但单元测试方法失败,我无法弄清楚原因。两者都使用相同 app.config 的副本(未添加为链接),并且都安装了 System.Configuration.ConfigurationManager v4.4.1 NuGet 包。

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="Test1" value ="This is test 1."/>
    <add key="Test2" value ="42"/>
    <add key="Test3" value ="-42"/>
    <add key="Test4" value="true"/>
    <add key="Test5" value="false"/>
    <add key="Test6" value ="101.101"/>
    <add key="Test7" value ="-1.2345"/>
  </appSettings>
</configuration>

单元测试

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Configuration;

namespace ConfigTest

    [TestClass]
    public class UnitTest1
    
        [TestMethod()]
        public void ConfigTest()
        
            foreach (string s in ConfigurationManager.AppSettings.AllKeys)
            
                System.Console.WriteLine(s);
                System.Diagnostics.Debug.WriteLine(s);
            

            //AllKeys.Length is 0? Should be 7...
            Assert.IsTrue(ConfigurationManager.AppSettings.AllKeys.Length == 7);
        
    

控制台应用

using System;
using System.Configuration;

namespace ConfigTestApp

    class Program
    
        static void Main(string[] args)
        
            foreach (string s in ConfigurationManager.AppSettings.AllKeys)
            
                Console.WriteLine(s);
                System.Diagnostics.Debug.WriteLine(s);
            

            //Outputs 7 as expected
            Console.WriteLine(ConfigurationManager.AppSettings.AllKeys.Length);
        
    
  

鉴于我对整个 .NET Core 世界还很陌生,我在这里做的事情是否完全不正确?我现在有点疯狂......

【问题讨论】:

您确定正确的 .config 与测试项目相关联,并且该测试项目没有您在测试中读取的自己的 .config? ConfigurationManager.AppSettings[0];工作吗? @PawełŁukasik - 我在整个解决方案目录中仅有的 2 个配置是您在上面看到的内容的副本(除了生成的 *.dll.configs)。属性窗口中列出的完整路径是我所期望的。 @MichaelEvanchik - 不。 ArgumentOutOfRangeException。 @MartinUllrich - 单元测试找到 0 个配置项(它应该找到 7 个 - 我将编辑代码以使其更清晰)。该应用程序按预期找到了所有 7 个。我是否正确理解了您的问题? 【参考方案1】:

虽然 app.config 存在于根项目文件夹中,但将以下字符串添加到 Post-build event command line

xcopy /Y $(ProjectDir)app.config $(ProjectDir)$(OutDir)testhost.dll.config*

【讨论】:

这个问题有 12 个现有答案,包括一个获得最高票数、获得 46 票 的被接受的答案。你确定你的解决方案还没有给出吗?如果不是,您为什么认为您的方法改进了已被社区验证的现有提案?在 Stack Overflow 上,提供解释总是很有用,但在问题已得到解决且让 OP 和社区都满意的情况下,它尤其很重要。通过解释您的答案有何不同以及何时可能更受欢迎,帮助读者了解答案。【参考方案2】:

幸运的是,现在有一种方法可以在运行时设置预期配置文件的名称。您可以为当前应用域设置APP_CONFIG_FILE数据。

我创建了以下 SetUpFixture 来自动执行此操作:

[SetUpFixture]
public class SetUpFixture

    [OneTimeSetUp]
    public void OneTimeSetUp()
    
        var testDllName = Assembly.GetAssembly(GetType())
                                  .GetName()
                                  .Name;
        var configName = testDllName + ".dll.config";
        AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", configName);
    

相关的 GitHub 讨论是:

ConfigurationManager doesn't find config file with "dotnet test" · Issue #22720 · dotnet/runtime Provide a way to override the global configuration file path · Issue #931 · dotnet/runtime Respect AppContext.SetData with APP_CONFIG_FILE key by krwq · Pull Request #56748 · dotnet/runtime

【讨论】:

【参考方案3】:

当我们回答这样经过充分研究和明确表达的问题时,我们最好假设它是由一个有见识和聪明的人提出的。而不是用明显的新的,伟大的方式来编写大量样板代码来解析各种 JSON 等,被强加给我们并被知识渊博的人推到我们的喉咙里,而不是光顾他们,我们应该专注于回答这一点.

由于 OP 已经使用System.Configuration 访问设置,他们已经知道如何到达这一点。唯一缺少的是一点点触动:将这一行添加到构建后事件中:

copy $(OutDir)<appname>.dll.config $(OutDir)testhost.dll.config

其中 是正在单元测试的项目。

我为仍在使用(最初是蹩脚但可行的)app.config 实现的每个人鼓掌,因为这样做可以保护我们和我们的客户对技术的投资,而不是重新发明***。阿门。

【讨论】:

【参考方案4】:

查看github 问题的 cmets,我发现了一个可以在 msbuild 文件中解决的解决方法...

<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
  <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.dll.config" />
</Target>

这使得在将配置数据移植到 json 配置文件之前更容易在 .NET Core 下验证现有测试。

编辑

如果在 Resharper 下运行,上一个答案不起作用,因为 Resharper 代理程序集,所以你需要

<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
  <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\ReSharperTestRunner64.dll.config" />
</Target>

【讨论】:

【参考方案5】:

.CORE 3.1 为了找出正在使用的 dll.config 文件,我通过添加这一行并查看值是什么来调试测试。

string path = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;

然后我发现 resharper 使用的是 testhost.dll.config 而 VStest 使用的是 testhost.x86.dll.config。我需要将以下行添加到项目文件中。

  <Target Name="CopyCustomContent" AfterTargets="AfterBuild">
    <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.dll.config" />
    <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.x86.dll.config" />
  </Target>

【讨论】:

testhost.x86.dll.config 也是我的 net core 3.0 项目所需的名称。这也适用于重命名 app.config -> testhost.x86.dll.config 并将复制到输出目录属性设置为始终复制。【参考方案6】:

对于我的 .NET-Core 和 .NET-Framework 混合项目,我在单元测试全局设置中添加了以下内容:

#if NETCOREAPP
using System.Configuration;
using System.IO;
using System.Reflection;
#endif

...

// In your global setup:
#if NETCOREAPP
    string configFile = $"Assembly.GetExecutingAssembly().Location.config";
    string outputConfigFile = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;
    File.Copy(configFile, outputConfigFile, true);
#endif

这会将配置文件复制到输出路径testhost.dll.config,但应该具有足够的弹性以应对测试框架的未来变化。

或者你可以复制到下面,这相当于:

string outputConfigFile = Path.Combine(Path.GetDirectoryName(configFile), $"Path.GetFileName(Assembly.GetEntryAssembly().Location).config");

感谢@stop-cran 和@PaulHatcher 的解决方案,这是两者的结合。

【讨论】:

【参考方案7】:

添加配置文件

首先,将 appconfig.json 文件添加到集成测试项目中

配置要复制到输出的 appconfig.json 文件 通过更新目录

添加 NuGet 包

Microsoft.Extensions.Configuration.Json

在单元测试中使用配置

[TestClass]
public class IntegrationTests



    public IntegrationTests()
    
        var config = new ConfigurationBuilder().AddJsonFile("appconfig.json").Build();

        _numberOfPumps = Convert.ToInt32(config["NumberOfPumps"]);

        _numberOfMessages = Convert.ToInt32(config["NumberOfMessages"]);

        _databaseUrl = config["DatabaseUrlAddress"];
    
 

【讨论】:

AddJsonFile 在 .net core 3.1 中不存在【参考方案8】:

如果您检查调用ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);的结果

它应该告诉您在为该程序集运行单元测试时所需的配置文件应该在哪里。

我发现 ConfigurationManager 不是在寻找 app.config 文件,而是在寻找 testhost.dll.config 文件。

这是针对以netcoreapp2.1 为目标的项目,并引用了Microsoft.NET.Test.SdkNUnit 3.11Nunit3TestAdapter 3.12.0

【讨论】:

查看下面的答案以获取将 app.config 文件添加到 testhost.dll.config 的方法 注意:除了将其从 app.config 重命名为 testhost.dll.config 才能正常工作外,我还必须将文件属性“复制到输出目录”设置为“始终复制”。感谢重命名提示!【参考方案9】:

一个 hacky 但可行的方法是将配置复制到与条目程序集相同的文件夹中,无论它是什么:

[SetUpFixture]
public class ConfigKludge

    [OneTimeSetUp]
    public void Setup() =>
        File.Copy(
            Assembly.GetExecutingAssembly().Location + ".config",
            Assembly.GetEntryAssembly().Location + ".config",
            true);

    [OneTimeTearDown]
    public void Teardown() =>
        File.Delete(Assembly.GetEntryAssembly().Location + ".config");

除了添加这个类之外,唯一让它工作的方法是在测试项目中包含app.config 文件(没有任何复制选项)。它应该在构建步骤中以&lt;your test project name&gt;.dll.config 的形式复制到输出文件夹,因为这是一种默认逻辑。

注意OneTimeSetUpAttribute的文档:

总结: 标识在运行任何子测试之前调用一次以执行设置的方法。

虽然它应该适用于单个项目的并行测试运行,但同时运行两个测试项目时可能会出现明显的问题,因为配置会被覆盖。

但是,它仍然适用于容器化测试运行,例如 Travis。

【讨论】:

【参考方案10】:

当您处理直接访问静态ConfigurationManager 属性(例如AppSettingsConnectionStrings)的代码时,此处给出的答案都没有提供可行的解决方法。

事实是,目前这是不可能的。您可以通读此处的讨论以了解原因: https://github.com/dotnet/corefx/issues/22101

这里有讨论实现对它的支持: https://github.com/Microsoft/vstest/issues/1758

在我看来,支持这种方案是有意义的,因为它一直在 .NET Framework 上工作,而且 System.Configuration.ConfigurationManager 现在是一个 .NET Standard 2.0 库。

【讨论】:

我完全同意你的最后一点。当然,这不是“新的/首选”的做事方式,但它确实可以在 .NET 中使用,并且当时对我来说似乎难以置信很奇怪,因为它在这里不起作用。【参考方案11】:

我在 xunit 测试中遇到了同样的问题,并通过使用 ConfigurationManager 中的配置实例解决了这个问题。在展示它在所有三个中的替代方式之前,我将它的静态(正常)方式放在核心、框架(但不是单元测试)中:

        var appSettingValFromStatic = ConfigurationManager.AppSettings["mySetting"];
        var appSettingValFromInstance = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["mySetting"].Value;

这是一个类似/相关的问题。如果有人需要获取某个部分,您可以执行类似的操作,但必须在应用配置中更改类型:

<configSections>
    <section name="customAppSettingsSection" type="System.Configuration.AppSettingsSection"/>
    <section name="customNameValueSectionHandlerSection" type="System.Configuration.NameValueSectionHandler"/>
</configSections>

<customAppSettingsSection>
    <add key="customKey" value="customValue" />
</customAppSettingsSection>

<customNameValueSectionHandlerSection>
    <add key="customKey" value="customValue" />
</customNameValueSectionHandlerSection>

抓取部分的代码:

        var valFromStatic = ((NameValueCollection)ConfigurationManager.GetSection("customNameValueSectionHandlerSection"))["customKey"];
        var valFromInstance = ((AppSettingsSection)ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).GetSection("customAppSettingsSection")).Settings["customKey"].Value;

我觉得我也疯了,而且我知道有更新的方式在核心中进行配置,但如果有人想做跨平台的事情,这是我知道的唯一方法。如果有人有替代品,我会很感兴趣

【讨论】:

【参考方案12】:

通常在 .NET Framework 项目中,任何 App.config 文件都由 Visual Studio 复制到 bin 文件夹,并带有可执行文件的名称 (myApp.exe.config),以便在运行时可以访问它。在 .NET Standard 或 Core Framework 中不再存在。您必须手动复制并设置 bin/debug 或 release 文件夹中的文件。之后它可能会得到类似的东西:

                string AssemblyName = System.IO.Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().GetName().CodeBase);
            AppConfig = (System.Configuration.Configuration)System.Configuration.ConfigurationManager.OpenExeConfiguration(AssemblyName);

【讨论】:

【参考方案13】:

ConfigurationManager API 将仅使用当前正在运行的应用程序的配置。在单元测试项目中,这意味着测试项目的 app.config,而不是控制台应用程序。

.NET Core 应用程序不应该使用 app.config 或 ConfigurationManager,因为它是一个遗留的“完整框架”配置系统。

考虑使用Microsoft.Extensions.Configuration 来读取 JSON、XML 或 INI 配置文件。请参阅此文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration

【讨论】:

我对您的第一段所暗示的内容有点困惑。单元测试项目确实有它自己的 app.config。 然后加一个,看看会发生什么。 测试项目已经有了?我的意思是你将 app.config 添加到单元测试项目中,看看 AppSettings 是否会出现。 您正在进行单元测试,在单元测试中,您的注意力应该是尝试测试的特定方法,并且应该消除无关的依赖项。在这种情况下,请尝试 mocking/moleing(使用 Microsoft Mole 和 Pex 或 moc)system.configuration 类 你有没有想过解决这个问题? ConfigurationManager 在框架、标准和核心中工作似乎很愚蠢,除了在单元测试中

以上是关于无法使用 ConfigurationManager 在 C# .NET Core 单元测试项目中读取 app.config的主要内容,如果未能解决你的问题,请参考以下文章

无法访问 Windows 窗体应用程序中的 ConfigurationManager.AppSettings

ConfigurationManager 类不可用

如何使用 moq 模拟 ConfigurationManager.AppSettings

ConfigurationManager姿势快闪

使用ConfigurationManager类读写配置文件

ConfigurationManager.AppSettings 性能问题