PAYATHON 2026

排查 .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。

备注:内容仅供参考。