【问题标题】:How to compare file paths from JsonConfigurationSources and Directory.GetFiles properly?如何正确比较 JsonConfigurationSources 和 Directory.GetFiles 的文件路径?
【发布时间】:2022-01-19 12:37:14
【问题描述】:

我创建了一个扩展方法,将所有 JSON 配置文件添加到 IConfigurationBuilder

public static class IConfigurationBuilderExtensions
{
    public static IConfigurationBuilder AddJsonFilesFromDirectory(
        this IConfigurationBuilder configurationBuilder,
        IFileSystem fileSystem,
        string pathToDirectory,
        bool fileIsOptional,
        bool reloadConfigurationOnFileChange,
        string searchPattern = "*.json",
        SearchOption directorySearchOption = SearchOption.AllDirectories)
    {
        var jsonFilePaths = fileSystem.Directory.EnumerateFiles(pathToDirectory, searchPattern, directorySearchOption);

        foreach (var jsonFilePath in jsonFilePaths)
        {
            configurationBuilder.AddJsonFile(jsonFilePath, fileIsOptional, reloadConfigurationOnFileChange);
        }

        return configurationBuilder;
    }
}

并希望使用 xUnit 为其创建测试。基于

How do you mock out the file system in C# for unit testing?

我安装了 System.IO.AbstractionsSystem.IO.Abstractions.TestingHelpers 包并开始测试是否已添加目录中的 JSON 文件

public sealed class IConfigurationBuilderExtensionsTests
{
    private const string DirectoryRootPath = "./";
    
    private readonly MockFileSystem _fileSystem;

    public IConfigurationBuilderExtensionsTests()
    {
        _fileSystem = new MockFileSystem(new[]
            {
                "text.txt", 
                "config.json", 
                "dir/foo.json", 
                "dir/bar.xml", 
                "dir/sub/deeper/config.json"
            }
            .Select(filePath => Path.Combine(DirectoryRootPath, filePath))
            .ToDictionary(
            filePath => filePath, 
            _ => new MockFileData(string.Empty)));
    }
    
    [Theory]
    [InlineData("*.json", SearchOption.AllDirectories)]
    [InlineData("*.json", SearchOption.TopDirectoryOnly)]
    // ... more theories go here ...
    public void ItShouldAddJsonFilesFromDirectory(string searchPattern, SearchOption searchOption)
    {
        var addedJsonFilePaths = new ConfigurationBuilder()
            .AddJsonFilesFromDirectory(_fileSystem, DirectoryRootPath, true, true, searchPattern, searchOption)
            .Sources
            .OfType<JsonConfigurationSource>()
            .Select(jsonConfigurationSource => jsonConfigurationSource.Path)
            .ToArray();
        
        var jsonFilePathsFromTopDirectory = _fileSystem.Directory.GetFiles(DirectoryRootPath, searchPattern, searchOption);
        
        Assert.True(addedJsonFilePaths.Length == jsonFilePathsFromTopDirectory.Length);
    
        for (int i = 0; i < addedJsonFilePaths.Length; i++)
        {
            Assert.Equal(
                jsonFilePathsFromTopDirectory[i],
                Path.DirectorySeparatorChar + addedJsonFilePaths[i]);
        }
    }
}

测试通过了,但我想知道在将Path.DirectorySeparatorChar 附加到addedJsonFilePaths[i] 时是否会遇到麻烦。

问题是

  • jsonFilePathsFromTopDirectory[i] 返回“/config.json”
  • addedJsonFilePaths[i] 返回“config.json”

所以我必须在开头加上一个斜杠。您对如何改进/避免以后出现问题有什么建议吗?

【问题讨论】:

    标签: c#


    【解决方案1】:

    /config.json 是绝对路径,config.json 是相对路径,因此要比较它们,您必须通过给它一个目录将相对路径转换为绝对路径。

    但这不是真正的问题,the document 不够详细(实际上根本没有提及)。

    当你通过AddJsonFile扩展方法添加路径时,会自动调用FileConfigurationSource.ResolveFileProvider

    如果没有设置文件提供程序,对于绝对路径,这将为最近的现有目录创建一个物理文件提供程序。

    此方法将绝对路径转换为相对路径,这就是为什么/config.json变成config.json,目录信息被放入自动生成的文件提供程序中。

    所以要正确使用API​​,你需要改变:

    jsonConfigurationSource.Path

    到:

    jsonConfigurationSource.FileProvider.GetFileInfo(jsonConfigurationSource.Path).PhysicalPath

    或者您可以提供FileProvider:

    configurationBuilder.AddJsonFile(new NullFileProvider(), jsonFilePath, fileIsOptional, reloadConfigurationOnFileChange);

    【讨论】:

    • 我认为你的解决方案是最好的,因为这样我就可以简单地检查两个数组是否相等:)
    【解决方案2】:

    您可以使用System.IO.Path.Combine,而不是自己添加目录分隔符,它会处理这个问题;它只在需要时添加一个。

    旁注:由于jsonFilePathsFromTopDirectory[i] 返回的/config.json 具有/ 而不是\,因此您可以考虑使用Path.AltDirectorySeparatorChar / 而不是Path.DirectorySeparatorChar \
    无论哪种方式,Path.Combine 都可以处理。


    以下两个语句都会导致/config.json

    Path.Combine(Path.AltDirectorySeparatorChar.ToString(), "/config.json");
    Path.Combine(Path.AltDirectorySeparatorChar.ToString(), "config.json");
    

    你的断言语句看起来像

    Assert.Equal(
        jsonFilePathsFromTopDirectory[i],
        Path.Combine(Path.AltDirectorySeparatorChar.ToString(), addedJsonFilePaths[i])
        );
    

    【讨论】:

    • 您好,谢谢您的回答!我个人更喜欢这个答案,因为这样我就不再需要循环了,我可以检查数组是否相等stackoverflow.com/a/70410993/7764329
    • @Question3r 没关系,通常有不止一种方法可以达到预期的结果。您可以选择最适合您的情况。
    【解决方案3】:

    比较文件的逻辑似乎没问题,我没有发现任何突出的问题,可以在前面加上“/”来匹配你需要的内容。 如果您也可以将System.IO.Path.DirectorySeparatorChar 用于目录根路径,那会更好,因此如果您在 Windows 或 Linux 上运行,您将不会有任何问题。

    但是您所做的事情可能存在概念上的问题。 据我了解,您的目标是验证程序正常工作所需的特定配置文件的存在,如果这些文件丢失,则程序应该失败。 但是由于缺少配置文件而导致的这种失败是您的代码的预期和有效结果。 但是,您对此进行单元测试,就好像丢失的文件应该无法通过测试一样,好像丢失的文件表明您的代码有问题,这是错误的。

    缺少文件并不表示您的代码工作不正确,并且不应将单元测试用作验证程序以确保文件在执行程序之前存在,您可能会同意单元测试不是实际的一部分过程,它应该只旨在测试您的代码而不是先决条件,测试应该比较预期的结果(代码的模拟结果)与实际结果,当然不意味着成为代码的一部分。该单元测试看起来像一个应该在代码中的验证器。

    因此,除非这些文件是由您的特定代码(而不是部署)生成的,否则没有意义进行测试。在这种情况下,您需要创建一个配置验证器代码 - 而您的单元测试可以测试它。因此,它将使用您提供的模拟输入来测试验证器的预期结果。但这里的问题是,您会知道您只测试了验证逻辑,而不是文件的实际存在。

    【讨论】:

      猜你喜欢
      • 2014-02-28
      • 2014-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-28
      相关资源
      最近更新 更多