2

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

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

4

5 回答 5

2

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

你说:假设我有一个函数

  1. 将数据库中的数据加载到数据集中,
  2. 格式加载数据(格式存储在一些外部 xml 文件中)和
  3. 最后将数据集序列化为文件。

听起来至少有3-4个工作......

如果您再拆分代码,那么您可以测试每个函数,而无需在它们周围使用所有其他粘性物。

如果你只想做 Dataset.WriteXML,那么你不必测试它。那是一个很好的框架功能。试着在里面放一些模拟来伪造它。具体如何取决于您的解决方案...

回复评论:

使用自己的测试创建所有这些小类将使测试变得容易,并且还会使您的函数小而紧凑(-> 易于测试)您将测试数据集的内容是否正是您需要的,而不是数据集正确序列化为 xml 文件。您还将测试您的格式化程序是否能正确执行其功能,而不依赖于任何其他逻辑。您还将测试数据访问,但不访问数据库(再次存根/模拟)

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

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

为了使测试更快,你不应该接触你无法控制的东西:

  • 网页服务
  • 文件系统
  • 数据库
  • Com 对象

为了使它们变得简单,您让您的课程专注于单个任务,即您已经提到的 SRP 所在的位置。看看这个答案……也会指出“SOLID”开发的其他原则

https://stackoverflow.com/questions/1423597/solid-principles/1423627#1423627

于 2009-09-22T16:20:38.403 回答
1

快速总结一下,如果你真的想让这个可测试,我建议:

  1. 将数据格式化代码提取到一个新类中(它实现了一个可以用来模拟的接口)。
  2. 通过这门课你的DataSet.
  3. 让新类返回 你也可以模拟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);
    }
}

这段代码简单有效,但不可测试(没有副作用)。此外,将所有这些逻辑放在一个地方违反了单一职责原则。

根据您的需要,这可能没问题!实现 SRP 始终只是一个目标,我们始终需要在可测试性与影响我们设计的其他因素之间取得平衡。

但是,如果您想进一步分离职责并使其可测试,您可以通过将格式化逻辑移动到它自己的类中来实现(这将实现IEmployeeDataSetFormatter),然后将一个IEmployeeDataSetFormatter服务的构造函数,就像IEmployeeRepository)。格式化数据的方法将返回一个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);
    }
}

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

于 2009-09-22T16:21:42.803 回答
1

参加额外的课程。测试时更容易:

  • 对于数据集导出器/写入器 - 检查修改后的数据是否已传递给写入器
  • 对于格式化程序 - 检查格式化逻辑

这并不意味着您不会:

  • 使用 dataSet.WriteXml,你只需在 v.simple 类中这样做......作为一个单行,你不需要为它添加一个集中的集成测试......但是如果你在商店里说在外部系统,你可以选择在其他实现中这样做,而不影响其他代码
  • 对数据集导出器/写入器使用单独的类,而不是简单的文件写入器。

只要您使用 v. 简单的依赖注入,您实际上只是保持一切简单......这将在短期和长期内为您带来更好的结果。

附言。在测试期间不访问文件系统很重要,因为当您的测试数量增加时,您希望它们快速运行,所以不要被最初出现的测试“快速”访问所误导

于 2009-09-22T17:04:56.217 回答
0

从您的描述中,您的代码在做什么并不完全清楚。您的代码正在做的最重要的事情是什么?是格式化吗?我想说不要费心测试 xml 的编写。无论如何,您将如何验证?

如果您必须读取和写入文件,您最好更改您的代码,以便传入一个流读取器/流写入器或文本读取器/文本写入器,调用代码注入一个实例,例如用于测试的内存流和用于真实 i/ 的文件流o 在生产中。

于 2009-09-22T16:11:37.660 回答
0

您可以使用WriteXml()带有Streamor的版本TextWriter并设置您的代码,以便将此对象传递到您的代码中,然后在模拟对象中进行测试传递。

于 2009-09-22T16:12:45.620 回答