如何注入表示文件夹集合的依赖项?

时间:2018-01-18 21:36:23

标签: c# testing dependency-injection dependencies software-design

依赖注入文件夹的最佳方法是什么?

我有一个需要三个文件夹的课程。目标是从子文件夹结构(包含文件排序的多个文件夹的文件夹)中收集文件,并将其写入另外两个子文件夹结构,可能是否通过抽象,它需要文件夹。

具体来说,我想将机器学习算法的数据分成training and test data,而子文件夹代表将要分类的图像的不同类别。

那么,在保持易于测试的代码的同时注入这些文件夹的最佳方法是什么? 我应该传递一个字符串吗?我应该传递FileInfo对象吗?我应该构建一个接口,它代表文件夹结构的包装器吗? 处理这个问题的最佳方法是什么?

C#方法是最好的,但没有必要。

如果缺少信息,请告诉我。

2 个答案:

答案 0 :(得分:1)

现在使用System.IO.Abstractions可以更轻松地表示文件系统的操作,而无需实际依赖文件系统。该模式类似于我们如何编写依赖于HttpContextWrapper而不是直接依赖于HttpContext的代码,这使我们可以模拟HttpContext

使用这些类,您可以注入IEnumerable<System.IO.Abstractions.DirectoryInfoWrapper>,并且在运行时每个目录都是一个“真实的” DirectoryInfo,其创建方式如下:

var directory = new DirectoryInfo("c:\folder");
var wrapper = new DirectoryInfoWrapper(new FileSystem(), directory);

DirectoryInfoWrapper的行为与DirectoryInfo相似,只是它还返回抽象。例如,wrapper.GetFiles()返回IFileInfo[]而不是FileInfo[]。因此,我们所有的代码都将依赖于抽象编写。很好,因为抽象与具体类具有相同的属性和方法。

或者,您可能想要这样的东西,而不是注入实际的目录:

public interface IDirectoryProvider
{
    IEnumerable<DirectoryInfoWrapper> GetDirectories(string someInput);
}

在任何一种情况下,这都允许您使用模拟目录进行单元测试,该目录在必要时包含更多模拟目录甚至模拟文件。我通常不喜欢返回模拟的模拟。如果比创建模拟文件更容易,您甚至可以让模拟目录返回测试项目中包含的真实文件。至少它提供了一些抽象之前无法使用的选项。


可拆分的细节:有人可以说这些并不是真正的“抽象”,因为从设计上讲,它们是具体类的精确表示。您可以使用它们来表示完全不同的内容,例如数据库存储,但是您可能不会,它们也不是很好的抽象,因为它会迫使您将伪路径映射到记录。

话虽这么说,我试图想象我将如何称呼命名空间而不是System.IO.Abstractions,但我想不出更好的办法。您可以将它们称为“嘲笑”,但是在生产代码中看到它们会令人困惑。

答案 1 :(得分:0)

无论您如何编写它,都无法测试在不使用实际文件和文件夹的情况下在文件夹之间移动文件的类。但就代表它而言,可能是这样的东西:

public interface ISomethingRepository
{
    IEnumerable<ThingWithDataInIt> GetThings();
    void SaveAsTraining(ThingWithDataInIt thing);
    void SaveAsTest(ThingWithDataInIt thing);
}

目的是,无论依赖于什么,它真的想要文件中的东西,并且它想知道已经检查了一个项目,它可以用&#34;培训&#34;数据或&#34;测试&#34;数据

实现可以基于文件系统。我只是为了说明而编制细节。我不知道这些文件中的内容,是否需要反序列化等等。也许对于每个文件,您必须解析这些行并返回一组内容。这是为了说明。

public class FileSystemSomethingRepository : ISomethingRepository
{
    private readonly string _sourceDirectoryPath;
    private readonly string _trainingDirectoryPath;
    private readonly string _testDirectoryPath;

    public FileSystemSomethingRepository(string sourceDirectoryPath, 
        string trainingDirectoryPath, 
        string testDirectoryPath)
    {
        _sourceDirectoryPath = sourceDirectoryPath;
        _trainingDirectoryPath = trainingDirectoryPath;
        _testDirectoryPath = testDirectoryPath;
    }

    public IEnumerable<ThingWithDataInIt> GetThings()
    {
        var filePaths = Directory.GetFiles(_sourceDirectoryPath);
        foreach (var filePath in filePaths)
        {
            var fileContent = File.ReadAllText(filePath);
            var deserialized = JsonConvert.DeserializeObject<ThingWithDataInIt>(fileContent);
            yield return deserialized;
        }
    }

    public void SaveAsTraining(ThingWithDataInIt thing)
    {
        // serialize it, write it to the folder
    }

    public void SaveAsTest(ThingWithDataInIt thing)
    {
        // serialize it, write it to the folder
    }
}

界面很容易被模拟,并且将保留依赖于此的任何类来了解数据是来自文件系统,它是如何序列化/反序列化的等等。从消费者那里隐藏这些细节是什么使它成为一种抽象,使你能够获得依赖注入的好处。

有助于您设计正确抽象的其他东西是编写您的界面,准确描述您希望依赖于它的类与之相关的内容。换句话说,从消费者的角度来编写界面。这样你就不会试图想象一个解决方案,同时试图弄清楚它是否会做你想要的。您可能需要进行一些调整,但首先要通过编写界面来确定您的课程需要什么。然后你弄清楚如何实现它。

这也使您能够首先关注最重要的任务。您想编写机器学习算法,而不是从文件中读取的东西。您可以编写代表您的类所需内容的界面,并继续前进,就好像已经存在实现一样。你可以专注于你更关心的事情,你甚至可以测试它。然后你可以回来写这样的实现细节。或者,如果您正在为团队工作,您可以向其他人提供界面并要求他们实施该界面。