排查 .NET Core 测试项目未从 app.config 加载连接字符串
结论
ConfigurationManager 读取的是当前进程的有效配置文件,不会根据调用代码所在的程序集自动加载对应的 .dll.config。
运行测试时,宿主通常是 testhost.dll,所以:
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
返回的配置文件是:
testhost.dll.config
CMS.AutoTestsSetup.dll.config 只是类库旁边的普通文件。即使已经复制到输出目录,ConfigurationManager.ConnectionStrings 也不会自动读取或合并其中的内容。
输出中的 LocalSqlServer 来自 machine.config,这说明类库配置中的 <clear /> 没有进入当前测试宿主的配置层级。
为什么会读取 machine.config
传统的 System.Configuration 配置具有继承关系。如果应用配置中缺少某个配置节,ConfigurationManager 仍可能返回 machine.config 中的默认设置。
当前输出已经给出了配置来源:
Source = ...\Config\machine.config
这里的 LocalSqlServer 是机器级默认配置,并非 CMS.AutoTestsSetup.dll.config 中定义的连接字符串。测试宿主没有加载类库配置,所以类库文件中的:
<clear />
也不会清除这条配置。
推荐做法:将配置放在测试项目中
测试运行时使用的连接字符串应由测试项目提供,不宜放在被引用的类库项目中。
可以在测试项目的 .csproj 中指定:
<PropertyGroup>
<AppConfig>app.config</AppConfig>
</PropertyGroup>
测试项目中的 app.config 应包含完整的配置结构:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<clear />
<add
name="LocalSqlServer"
connectionString="DataSource=etc" />
<add
name="DataWarehouse"
connectionString="DataSource=etc" />
</connectionStrings>
</configuration>
测试程序集配置能否转换为测试宿主使用的 testhost.dll.config,取决于测试 SDK、运行器和目标框架的组合。升级 Microsoft.NET.Test.Sdk、IDE 或目标框架后,这项行为可能发生变化。即使输出目录中存在 TestProject.dll.config 或 CMS.AutoTestsSetup.dll.config,也不能据此认定测试宿主已经加载了它。
验证时应查看实际使用的配置路径:
var config = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None);
Console.WriteLine(config.FilePath);
foreach (ConnectionStringSettings item in config.ConnectionStrings.ConnectionStrings)
{
Console.WriteLine(
$"{item.Name} = {item.ConnectionString}, " +
$"Source = {item.ElementInformation.Source}");
}
如果这里仍显示 testhost.dll.config,并且该文件中没有所需的 <connectionStrings>,静态属性 ConfigurationManager.ConnectionStrings 就不会返回类库配置中的值。
必须保留类库配置时,显式加载文件
如果需要将 CMS.AutoTestsSetup.dll.config 保留为独立配置文件,可以通过 ExeConfigurationFileMap 明确指定文件路径:
using System;
using System.Configuration;
using System.IO;
var configPath = Path.Combine(
AppContext.BaseDirectory,
"CMS.AutoTestsSetup.dll.config");
var fileMap = new ExeConfigurationFileMap
{
ExeConfigFilename = configPath
};
var config = ConfigurationManager.OpenMappedExeConfiguration(
fileMap,
ConfigurationUserLevel.None);
var connectionString =
config.ConnectionStrings.ConnectionStrings["DataWarehouse"]
?.ConnectionString;
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new ConfigurationErrorsException(
$"未在 {configPath} 中找到连接字符串 DataWarehouse。");
}
显式打开文件后,需要从返回的 config 对象中读取数据。下面的代码仍会读取测试宿主配置,调用 OpenMappedExeConfiguration 不会改变它的配置来源:
ConfigurationManager.ConnectionStrings["DataWarehouse"]
同时还要确保配置文件已复制到测试输出目录。例如,可以在测试项目中加入:
<ItemGroup>
<None Include="..\CMS.AutoTestsSetup\app.config"
Link="CMS.AutoTestsSetup.dll.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
路径和文件名需要按实际项目结构调整。在 Linux 或其他区分大小写的文件系统上,CMS.AutoTestsSetup.Dll.Config 和 CMS.AutoTestsSetup.dll.config 会被视为不同的文件名。
更适合 .NET 的配置方式
现代 .NET 测试项目可以使用 appsettings.json 和 Microsoft.Extensions.Configuration,由测试代码直接加载配置,避免依赖测试宿主处理传统 app.config。
appsettings.json:
{
"ConnectionStrings": {
"LocalSqlServer": "DataSource=etc",
"DataWarehouse": "DataSource=etc"
}
}
测试项目的 .csproj:
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
读取配置:
using Microsoft.Extensions.Configuration;
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.AddEnvironmentVariables()
.Build();
var connectionString =
configuration.GetConnectionString("DataWarehouse");
这种方式不受 testhost.dll.config 选择规则的影响,也方便通过环境变量覆盖测试数据库地址。
为什么以前正常、最近才出问题
现有信息不足以判断具体是哪次更新改变了行为。常见原因包括:
- 测试项目从 .NET Framework 迁移到了现代 .NET。
- 目标框架发生变化,例如升级到新的
netX.0。 Microsoft.NET.Test.Sdk、测试适配器或 IDE 更新后更换了测试宿主。- 之前负责复制或重命名配置文件的构建步骤已不再执行。
- 之前的运行器使用测试程序集配置作为应用配置,现在改为由
testhost.dll.config承载。
排查时可以对比升级前后的目标框架、Microsoft.NET.Test.Sdk 版本和测试命令,并检查实际生成的 testhost.dll.config。无论是哪项变化导致的,类库自己的 .dll.config 都不会自动参与全局配置合并。
可以先把测试配置移到测试项目,并确认测试宿主已经加载该配置。如果必须单独保留类库配置,就通过 OpenMappedExeConfiguration 显式读取。新项目可以直接使用 appsettings.json。
备注:内容仅供参考。
