Unit testing 文件访问、单元测试、依赖注入

Unit testing 文件访问、单元测试、依赖注入,unit-testing,dependency-injection,file-access,Unit Testing,Dependency Injection,File Access,我最近问了一个问题,关于将业务逻辑与数据访问分离以使应用程序可测试()的最佳方法。感谢Jeff Sternal,我为数据访问创建了接口,并将其具体实现从应用程序顶层传递到了BL。但现在我正试图弄清楚如何将文件访问方法与业务逻辑分离 假设我有一个函数,它将数据从数据库加载到数据集,格式化加载的数据(格式存储在某个外部xml文件中),最后将数据集序列化为文件。所以,为了支持可测试性,我需要将访问文件系统的所有函数移动到某个接口。但首先,我发现只调用dataset.WriteXml(文件)非常方便,但

我最近问了一个问题,关于将业务逻辑与数据访问分离以使应用程序可测试()的最佳方法。感谢Jeff Sternal,我为数据访问创建了接口,并将其具体实现从应用程序顶层传递到了BL。但现在我正试图弄清楚如何将文件访问方法与业务逻辑分离


假设我有一个函数,它将数据从数据库加载到数据集,格式化加载的数据(格式存储在某个外部xml文件中),最后将数据集序列化为文件。所以,为了支持可测试性,我需要将访问文件系统的所有函数移动到某个接口。但首先,我发现只调用dataset.WriteXml(文件)非常方便,但为了可测试性,我必须创建接口并将dataset.WriteXml()移动到它的实现中,这在我看来是不必要的层,并使代码不那么明显。第二,如果我将所有访问文件系统的方法移到一个接口上,这将违反SRP原则,因为序列化\反序列化数据集和从文件中读取数据格式似乎是不同的职责,对吗?

从您的描述中不完全清楚您的代码在做什么。代码所做的最重要的事情是什么?是福马汀吗?我想说的是,不要费心测试xml的编写。无论如何,你将如何验证这一点


如果您必须读取和写入文件,那么最好修改代码,以便传入streamreader/streamwriter或textreader/textwriter,调用代码将注入一个类似memorystream的实例,用于测试,filestream用于生产中的实际i/o。

您可以使用
WriteXml()的版本
它接受一个
文本编写器
,并设置您的代码,以便将此对象传递到您的代码中,然后用于测试模拟对象中的传递。

我认为您需要进一步拆分代码

你说: 假设我有一个函数

  • 将数据从数据库加载到数据集
  • 格式化加载的数据(格式存储在一些外部xml文件中)和
  • 最后将数据集序列化为文件
  • 听起来至少有3-4份工作

    如果您将代码进一步拆分,那么您就可以测试这些函数中的每一个,而不需要所有其他的代码

    如果您只想执行Dataset.WriteXML,那么您不必测试它。这是一个运行得很好的框架函数。试着在里面装些模子来伪装一下。具体如何取决于您的解决方案

    对评论的答复: 使用自己的测试创建所有这些小类将使测试变得容易,同时也将使函数变得小巧紧凑(->易于测试)。您将测试数据集的内容是否完全符合您的要求,而不是数据集是否正确序列化为xml文件。您还将测试格式化程序是否能够正确执行其功能,而不依赖于任何其他逻辑。您还将测试数据访问,但不访问数据库(再次使用存根/模拟)

    在您知道所有这些都正常工作之后,您“只是”验证数据集上的propper方法是否会被调用,这应该让您满意,因为您已经单独测试了其他部分

    单元测试的棘手部分是获得有意义的完整测试。它们应该是-快速-简单-

    为了加快测试速度,您不应触摸无法控制的物品:

    • 网络服务
    • 文件系统
    • 数据库
    • Com对象
    为了使它们变得简单,您需要将您的课程集中在一项任务上,即您已经提到的SRP所涉及的任务。看看这个答案。。。它还将指出“稳健”发展的其他原则


    要快速总结,如果您真的想使此可测试,我建议:

  • 提取数据格式 编码到一个新类(其中 实现一个可以使用的接口 嘲笑)
  • 数据集
    传递给此类
  • 使新类返回
    DataSet
    作为
    IXmlSerializable
    你也可以嘲笑
  • 在没有看到当前代码的情况下,我必须做出一些假设(希望不要太多!)-因此,当前代码可能如下所示:

    public class EmployeeService {
    
        private IEmployeeRepository _Repository;
    
        public EmployeeService(IRepository repository) {
            this._Repository = repository;
        }
    
        public void ExportEmployeeData(int employeeId, string path) {
    
            DataSet dataSet = this._Repository.Get(employeeId);
            // ... Format data in the dataset here ...
           dataSet.WriteXml(path);
        }
    }
    
    此代码简单有效,但不可测试(无副作用)。此外,将所有这些逻辑放在一个地方违反了单一责任原则

    根据您的需要,这可能很好,我们总是需要平衡可测试性和影响我们设计的其他因素

    但是,如果您想进一步分离责任并使其可测试,可以通过将格式化逻辑移动到其自己的类中(将实现
    IEmployedatasetFormatter
    ),然后将
    IEmployedatasetFormatter
    注入此方法调用来实现(或者,我们可以将其注入服务的构造函数中,就像
    IEEmployeeRepository
    )。格式化数据的方法将返回一个
    IXmlSerializable
    ,因此我们可以模拟它进行安全、独立的测试:

    public interface IEmployeeDataSetFormatter {
        IXmlSerializable FormatForExport(DataSet dataSet);
    }
    
    public class EmployeeDataSetFormatter: IEmployeeDataSetFormatter {
        public IXmlSerializable FormatForExport(DataSet dataSet) {
            // ... Format data in the dataset here ...
            return (IXmlSerializable) dataSet;
        }
    }
    
    public void ExportEmployeeData2(int employeeId, string path, IEmployeeDataSetFormatter formatter) {
    
        DataSet dataSet = this._Repository.Get(employeeId);
    
        IXmlSerializable xmlSerializable = formatter.FormatForExport(dataSet);
    
        // This is still an intermediary step - it's probably worth
        // moving this logic into its own class so you don't have to deal
        // with the concrete FileStream underlying the XmlWriter here
        using (XmlWriter writer = XmlWriter.Create(path)) {
            xmlSerializable.WriteXml(writer);
        }
    }
    

    这有一定的成本。它增加了一个额外的接口、一个额外的类和一点复杂性。但它是可测试的,并且更模块化(我认为这是一个好方法)。

    使用额外的类。在测试它时,更容易:

    • 对于数据集导出器/写入器-检查修改后的数据是否传递给写入器
    • 对于格式化程序-检查格式化逻辑
    这并不意味着你不:

    • 使用dataSet.WriteXml,您只需在v.simple类中执行此操作…作为一个一行程序,您不需要为其添加一个集中的集成测试…但是如果存储上的u l8r是在一个外部系统中,您可能会选择在另一个实现中执行此操作,而不受影响