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

发布于 2024-08-05 16:07:54 字数 336 浏览 6 评论 0原文

我最近问了一个关于将业务逻辑与数据访问分离以使应用程序可测试的最佳方法的问题(

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

I've recently asked a question regarding best ways of separating business logic from data access to make application testable (here). Thanks to Jeff Sternal, I've created interface for the data access and passing its concrete implementation from the application top level to BL. But now I'm trying to figure out how can I separate file access methods from business logic.

Let's say I have a function that loads data from a database into dataset, formats loaded data (formats are stored in some external xml file) and finally serializes dataset to a file. So, to support testability I need to move all the functions that access file system to some interface. But first - I find it quite handy to just call dataset.WriteXml(file), but for the testability I have to create interface and move dataset.WriteXml() to its implementation, which looks to me as unnecessary layer and makes code less obvious. And second - if I move all methods that access file system to a single interface, it will violate SRP principle, because serializing\deserializing datasets and reading data formats from a file seems to be different responsibilities, correct?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

落在眉间の轻吻 2024-08-12 16:07:55

您可以使用采用 StreamTextWriterWriteXml() 版本并设置您的代码,以便将此对象传递到您的代码中,然后传入模拟对象进行测试。

You could use the versions of WriteXml() that takes a Stream or TextWriter and set up your code such that this object is passed into your code, then for testing pass in a mock object.

眼睛会笑 2024-08-12 16:07:55

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

如果您必须读取和写入文件,您可能最好更改代码,以便传入流读取器/流写入器或文本读取器/文本写入器,调用代码注入诸如用于测试的内存流和用于实际 i/ 的文件流之类的实例。 Ø 生产中。

It's not completely clear from your description what your code is doing. What is the most important thing your code is doing? Is it the fomatting? I would say don't bother testing the writing of the xml. How would you verify that anyway?

If you must read from and write to files, you might be better altering your code so that you pass in a streamreader/streamwriter or textreader/textwriter, with the calling code injecting an instance of something like memorystream for testing and filestream for real i/o in production.

粉红×色少女 2024-08-12 16:07:54

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

你说:
假设我有一个函数,可以

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

这听起来至少需要 3-4 个工作...

如果您将代码再拆分一些,那么您就可以测试每个函数,而无需周围的所有其他功能。

如果您只想执行 Dataset.WriteXML,那么您不必测试它。这是一个运行良好的框架功能。尝试在那里添加一些模拟来伪造它。具体如何取决于您的解决方案...

评论答案:

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

在您知道所有这些都按预期工作后,您“只需”验证数据集上的正确方法是否将被调用,并且应该满足你,因为你已经单独测试了其他部分。

单元测试的棘手部分是进行有意义的测试。它们应该是 -快速 - 和 -简单 -

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

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

为了使它们简单,你让你的类专注于一个任务,即SRP 进来了,你已经提到了。看看这个答案...它还会指出“SOLID”开发的其他原则

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

I think you need to split up your code a little bit more...

You say:
Let's say I have a function that

  1. loads data from a database into dataset,
  2. formats loaded data (formats are stored in some external xml file) and
  3. finally serializes dataset to a file.

That sounds like 3-4 jobs at least...

If you split your code some more, then you can test each of those functions without all the other goo around them.

If you just want to do Dataset.WriteXML, Then you dont have to test that. Thats a Framework function thats working quite nicely. Try getting some mocks in there to fake it out. How exactly depends on your solution...

Answer to comment:

Creating all those small classes with their own tests will make testing easy, and it will also make your functions small and compact (-> easy to test) You would test if the content of the Dataset is exactly what you require, not if the dataset gets serialized correctly into an xml file. You would also test if your formater would perform its function correctly, without any dependencies to any other logic. You would also test the Data access, but without accessing the DB(Stubs/Mocks again)

After you know that all this works like it should, you "just" verify that the propper method on the dataset will get called, and that should satisfy you, since you tested the other parts in isolation already.

The tricky part about unittesting is getting meaningfull tests. They should be -fast- and -simple-

To make the tests fast, you should not touch stuff you have no control over:

  • Webservices
  • Filesystems
  • Databases
  • Com Objects

To make them simple, you keep you clases focused on a single task, that where the SRP comes in, which you already mentioned. Take a look at this answer... It will also point out the other principles of "SOLID" development

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

尐偏执 2024-08-12 16:07:54

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

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

这并不意味着您不这样做:

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

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

诗。在测试期间不访问文件系统很重要,因为当您的测试数量增加时,您希望它们运行得更快,因此不要被误导到测试的第一次“快速”访问

Go with the extra classes. When testing it its easier to:

  • For the dataset exporter/writer - check the modified data is passed to the writer
  • For the formatter - check the formatting logic

This doesn't mean you don't:

  • Use dataSet.WriteXml, you just do so in the v. simple class ... as a one liner, you don't need to do add a focused integration test for it ... but if u l8r on store say in an external system, u might choose to do so in that other implementation, without affecting the other code
  • Use separate classes for a dataset exporter/writter than for a simple file writer.

As long as you are using v. simple dependency injection, you are really just keeping everything simple ... which will get you better results both in the short and in the long run.

Ps. not hitting the file system during tests is important, as when your number of tests grow you want them to run v. fast, so don't be misguided to what appears at first "quick" accesses for a test

终难愈 2024-08-12 16:07:54

为了快速总结,如果您确实想让这个可测试,我建议:

  1. 提取数据格式
    将代码写入一个新类(其中
    实现一个您可以使用的接口
    嘲笑)。
  2. 将此类传递给您的DataSet
  3. 让新类返回
    DataSet 作为 IXmlSerialized
    你也可以嘲笑。

在没有看到当前代码的情况下,我必须做出一些假设(希望不要太多!) - 所以当前代码可能看起来像这样:

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 一样)。格式化数据的方法将返回一个 IXmlSerialized,以便我们可以模拟它以进行安全、隔离的测试:

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);
    }
}

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

For a quick summary, if you really want to make this testable, I recommend:

  1. Extracting the data formatting
    code into a new class (which
    implements an interface you can use
    to mock).
  2. Passing this class your DataSet.
  3. Make the new class return the
    DataSet as an IXmlSerializable
    that you can also mock.

Without seeing the current code, I have to make some assumptions (hopefully not too many!) - so perhaps the current code looks something like this:

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);
    }
}

This code is simple and effective, but not testable (without side-effects). Additionally, having all that logic in one place is a violation of the single-responsibility principal.

Depending on your needs, that's probably fine! Fulfilling the SRP is always just a goal, and we always need to balance testability with other factors that influence our designs.

However, if you want to segregate responsibilities a bit more and make it testable, you could do so by moving the formatting logic into its own class (which will implement IEmployeeDataSetFormatter), then inject an IEmployeeDataSetFormatter into this method call (alternately we could inject it into the service's constructor, just like the IEmployeeRepository). The method that formats the data will return an IXmlSerializable so we can mock it for safe, isolated testing:

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);
    }
}

This has definite costs. It adds an additional interface, an additional class, and a bit more complexity. But it's testable and more modular (in a good way, I think).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文