无法使用 ConfigurationManager 在 C# .NET Core 单元测试项目中读取 app.config
Can't read app.config in C# .NET Core unit test project with ConfigurationManager
我创建了一个简单的单元测试项目来读取 app.config 文件。目标框架是 Core 2.0。我还创建了一个 Core 2.0 控制台应用程序,以健全地检查自己以确保我没有做任何奇怪的事情(相同的测试按预期在 .NET 4.6.1 单元测试项目中通过)。
控制台应用程序读取 app.config 正常,但单元测试方法失败,我无法弄清楚原因。两者都使用相同 app.config 的副本(未添加为 link)并且都安装了 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 世界还是很陌生,我在这里做的事情是不是完全不正确?我现在有点疯狂......
ConfigurationManager
API 将仅使用当前 运行 的应用配置。在单元测试项目中,这意味着测试项目的 app.config,而不是控制台应用程序。
.NET Core 应用程序不应使用 app.config 或 ConfigurationManager
,因为它是遗留的 "full framework" 配置系统。
考虑使用 Microsoft.Extensions.Configuration
来读取 JSON、XML 或 INI 配置文件。请参阅此文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration
通常在 .NET Framework 项目中,任何 App.config 文件都被 Visual Studio 复制到 bin 文件夹,并带有可执行文件的名称 (myApp.exe.config),以便可以访问在运行时。 .NET Standard 或 Core Framework 中不再存在。您必须手动复制并设置bin/debug或发布文件夹中的文件。之后它可以用类似的东西得到:
string AssemblyName = System.IO.Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().GetName().CodeBase);
AppConfig = (System.Configuration.Configuration)System.Configuration.ConfigurationManager.OpenExeConfiguration(AssemblyName);
我在 xunit 测试中遇到了同样的问题,并通过使用 ConfigurationManager 中的 Configuration 实例解决了这个问题。在我展示它在所有三个中工作的替代方式之前,我将它的静态(正常)方式放在核心、框架(但不是单元测试)中:
var appSettingValFromStatic = ConfigurationManager.AppSettings["mySetting"];
var appSettingValFromInstance = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["mySetting"].Value;
这是一个 similar/related 问题。如果有人需要获取一个部分,您可以做类似的事情,但必须在应用程序配置中更改类型:
<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;
我觉得我也很疯狂,而且我知道有更新的方法可以在核心中进行配置,但是如果有人想做一些跨平台的事情,这是我知道的唯一方法。如果有人有其他选择,我会很感兴趣
当您处理直接访问静态 ConfigurationManager
属性(例如 AppSettings
或 ConnectionStrings
的代码时,此处给出的 None 答案提供了一个可行的解决方法。
事实上,目前还不可能。您可以阅读此处的讨论以了解原因:
https://github.com/dotnet/corefx/issues/22101
这里讨论了如何实现对它的支持:
https://github.com/Microsoft/vstest/issues/1758
在我看来,支持这种情况是有意义的,因为它一直在 .NET Framework 上工作,而且 System.Configuration.ConfigurationManager
现在是 .NET Standard 2.0 库。
一个 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");
}
除了添加这个 class 之外,唯一让它起作用的方法是在测试项目中包含 app.config
文件(没有任何复制选项)。它应该在构建步骤中作为 <your test project name>.dll.config
复制到输出文件夹,因为它是一种默认逻辑。
注意 OneTimeSetUpAttribute
的文档:
Summary:
Identifies a method that is called once to perform setup before any child tests are run.
虽然它应该适用于单个项目的并行测试运行,但当 运行 同时有两个测试项目时可能会出现明显的问题,因为配置会被覆盖。
但是,它仍然适用于容器化测试运行,例如 Travis。
如果您检查 ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
的调用结果
它应该会告诉您在对该程序集进行 运行 单元测试时所需的配置文件应该在哪里。
我发现 ConfigurationManager 寻找的不是 app.config
文件,而是 testhost.dll.config
文件。
这是针对 netcoreapp2.1
的项目,参考了 Microsoft.NET.Test.Sdk
、NUnit 3.11
和 Nunit3TestAdapter 3.12.0
查看 github 问题的评论,我发现了一个可以在 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>
添加配置文件
首先,在集成测试项目中添加一个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"];
}
}
对于我的混合 .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 的解决方案,这是这两者的组合。
.核心 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>
当我们回答这样一个经过充分研究和明确表达的问题时,我们最好假设它是由一个见多识广、聪明的人问的。而不是用明显的新的、伟大的方式来光顾他们,编写大量的样板代码来解析各种 JSON 等,被知识渊博的人强加给我们并推到我们的喉咙,我们应该专注于回答重点。
由于 OP 已经在使用 System.Configuration
访问设置,他们已经知道如何到达这一点。唯一缺少的是一点点:将此行添加到 post-build 事件:
copy $(OutDir)<appname>.dll.config $(OutDir)testhost.dll.config
其中 是进行单元测试的项目。
我为仍在使用(最初很蹩脚但可行)实施 app.config
的每个人鼓掌,因为这样做可以保护我们和我们客户的技术投资,而不是重新发明轮子。阿们。
幸运的是,现在有一种方法可以在运行时设置预期配置文件的名称。您可以为当前应用域设置 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讨论是:
虽然根项目文件夹中存在 app.config,但将以下字符串添加到 Post-build event command line
xcopy /Y $(ProjectDir)app.config $(ProjectDir)$(OutDir)testhost.dll.config*
我创建了一个简单的单元测试项目来读取 app.config 文件。目标框架是 Core 2.0。我还创建了一个 Core 2.0 控制台应用程序,以健全地检查自己以确保我没有做任何奇怪的事情(相同的测试按预期在 .NET 4.6.1 单元测试项目中通过)。
控制台应用程序读取 app.config 正常,但单元测试方法失败,我无法弄清楚原因。两者都使用相同 app.config 的副本(未添加为 link)并且都安装了 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 世界还是很陌生,我在这里做的事情是不是完全不正确?我现在有点疯狂......
ConfigurationManager
API 将仅使用当前 运行 的应用配置。在单元测试项目中,这意味着测试项目的 app.config,而不是控制台应用程序。
.NET Core 应用程序不应使用 app.config 或 ConfigurationManager
,因为它是遗留的 "full framework" 配置系统。
考虑使用 Microsoft.Extensions.Configuration
来读取 JSON、XML 或 INI 配置文件。请参阅此文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration
通常在 .NET Framework 项目中,任何 App.config 文件都被 Visual Studio 复制到 bin 文件夹,并带有可执行文件的名称 (myApp.exe.config),以便可以访问在运行时。 .NET Standard 或 Core Framework 中不再存在。您必须手动复制并设置bin/debug或发布文件夹中的文件。之后它可以用类似的东西得到:
string AssemblyName = System.IO.Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().GetName().CodeBase);
AppConfig = (System.Configuration.Configuration)System.Configuration.ConfigurationManager.OpenExeConfiguration(AssemblyName);
我在 xunit 测试中遇到了同样的问题,并通过使用 ConfigurationManager 中的 Configuration 实例解决了这个问题。在我展示它在所有三个中工作的替代方式之前,我将它的静态(正常)方式放在核心、框架(但不是单元测试)中:
var appSettingValFromStatic = ConfigurationManager.AppSettings["mySetting"];
var appSettingValFromInstance = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["mySetting"].Value;
这是一个 similar/related 问题。如果有人需要获取一个部分,您可以做类似的事情,但必须在应用程序配置中更改类型:
<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;
我觉得我也很疯狂,而且我知道有更新的方法可以在核心中进行配置,但是如果有人想做一些跨平台的事情,这是我知道的唯一方法。如果有人有其他选择,我会很感兴趣
ConfigurationManager
属性(例如 AppSettings
或 ConnectionStrings
的代码时,此处给出的 None 答案提供了一个可行的解决方法。
事实上,目前还不可能。您可以阅读此处的讨论以了解原因: https://github.com/dotnet/corefx/issues/22101
这里讨论了如何实现对它的支持: https://github.com/Microsoft/vstest/issues/1758
在我看来,支持这种情况是有意义的,因为它一直在 .NET Framework 上工作,而且 System.Configuration.ConfigurationManager
现在是 .NET Standard 2.0 库。
一个 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");
}
除了添加这个 class 之外,唯一让它起作用的方法是在测试项目中包含 app.config
文件(没有任何复制选项)。它应该在构建步骤中作为 <your test project name>.dll.config
复制到输出文件夹,因为它是一种默认逻辑。
注意 OneTimeSetUpAttribute
的文档:
Summary: Identifies a method that is called once to perform setup before any child tests are run.
虽然它应该适用于单个项目的并行测试运行,但当 运行 同时有两个测试项目时可能会出现明显的问题,因为配置会被覆盖。
但是,它仍然适用于容器化测试运行,例如 Travis。
如果您检查 ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
它应该会告诉您在对该程序集进行 运行 单元测试时所需的配置文件应该在哪里。
我发现 ConfigurationManager 寻找的不是 app.config
文件,而是 testhost.dll.config
文件。
这是针对 netcoreapp2.1
的项目,参考了 Microsoft.NET.Test.Sdk
、NUnit 3.11
和 Nunit3TestAdapter 3.12.0
查看 github 问题的评论,我发现了一个可以在 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>
添加配置文件
首先,在集成测试项目中添加一个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"];
}
}
对于我的混合 .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 的解决方案,这是这两者的组合。
.核心 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>
当我们回答这样一个经过充分研究和明确表达的问题时,我们最好假设它是由一个见多识广、聪明的人问的。而不是用明显的新的、伟大的方式来光顾他们,编写大量的样板代码来解析各种 JSON 等,被知识渊博的人强加给我们并推到我们的喉咙,我们应该专注于回答重点。
由于 OP 已经在使用 System.Configuration
访问设置,他们已经知道如何到达这一点。唯一缺少的是一点点:将此行添加到 post-build 事件:
copy $(OutDir)<appname>.dll.config $(OutDir)testhost.dll.config
其中
我为仍在使用(最初很蹩脚但可行)实施 app.config
的每个人鼓掌,因为这样做可以保护我们和我们客户的技术投资,而不是重新发明轮子。阿们。
幸运的是,现在有一种方法可以在运行时设置预期配置文件的名称。您可以为当前应用域设置 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讨论是:
虽然根项目文件夹中存在 app.config,但将以下字符串添加到 Post-build event command line
xcopy /Y $(ProjectDir)app.config $(ProjectDir)$(OutDir)testhost.dll.config*