单元测试文件 I/O
通读 Stack Overflow 上现有的单元测试相关线程,我找不到关于如何对文件 I/O 操作进行单元测试的明确答案。我最近才开始研究单元测试,之前已经意识到单元测试的优点,但很难习惯先编写测试。我已经将我的项目设置为使用 NUnit 和 Rhino Mocks,尽管我了解它们背后的概念,但在理解如何使用 Mock 对象时遇到了一些困难。
具体来说,我有两个问题想得到解答。首先,对文件 I/O 操作进行单元测试的正确方法是什么?其次,在我尝试了解单元测试的过程中,我遇到了依赖注入。在 Ninject 设置并工作后,我想知道是否应该在单元测试中使用 DI,或者直接实例化对象。
Reading through the existing unit testing related threads here on Stack Overflow, I couldn't find one with a clear answer about how to unit test file I/O operations. I have only recently started looking into unit testing, having been previously aware of the advantages but having difficulty getting used to writing tests first. I have set up my project to use NUnit and Rhino Mocks and although I understand the concept behind them, I'm having a little trouble understanding how to use Mock Objects.
Specifically I have two questions that I would like answered. First, what is the proper way to unit test file I/O operations? Second, in my attempts to learn about unit testing, I have come across dependency injection. After getting Ninject set up and working, I was wondering whether I should use DI within my unit tests, or just instantiate objects directly.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我使用 System.IO.Abstractions NuGet 包。
该网站有一个很好的示例,向您展示如何使用注入进行测试。
http://dontcodetired.com/blog/post /Unit-Testing-C-File-Access-Code-with-SystemIOAbstractions
这是从网站复制的代码副本。
I use the
System.IO.Abstractions
NuGet package.This web site has a nice example showing you how to use injection for testing.
http://dontcodetired.com/blog/post/Unit-Testing-C-File-Access-Code-with-SystemIOAbstractions
Here is a copy of the code copied from the web site.
自 2012 年起,您可以使用 Microsoft Fakes< /a> 无需更改您的代码库,例如因为它已经被冻结。
首先 为 System.dll 或任何其他包生成一个假程序集,然后模拟预期返回,如下所示:
Since 2012, you can do that using Microsoft Fakes without the need to change your codebase for example because it was frozen already.
First generate a fake assembly for System.dll - or any other package and then mock expected returns as in:
目前,我通过依赖注入使用 IFileSystem 对象。对于生产代码,包装类实现接口,包装我需要的特定 IO 函数。测试时,我可以创建一个 null 或存根实现并将其提供给被测类。被测试的班级却一无所知。
Currently, I consume an IFileSystem object via dependency injection. For production code, a wrapper class implements the interface, wrapping specific IO functions that I need. When testing, I can create a null or stub implementation and provide that to the class under test. The tested class is none the wiser.
测试文件系统时不一定需要做任何事情。事实上,根据具体情况,您可以做几件事。
您需要问的问题是:我在测试什么?
文件系统是否正常工作?您可能不需要测试该 除非您使用的是您非常不熟悉的操作系统。因此,例如,如果您只是发出保存文件的命令,那么编写测试以确保它们确实保存就是浪费时间。
文件被保存到正确的位置?那么,你怎么知道正确的位置是什么?假设您有将路径与文件名组合在一起的代码。这是您可以轻松测试的代码:您的输入是两个字符串,您的输出应该是一个字符串,该字符串是使用这两个字符串构造的有效文件位置。
您从目录中获取了正确的文件集吗?您可能必须为文件获取器类编写一个测试来真正测试文件系统。但是您应该使用其中包含不会更改的文件的测试目录。您还应该将此测试放入集成测试项目中,因为这不是真正的单元测试,因为它取决于文件系统。
但是,我需要对收到的文件做一些事情。对于该测试,您应该为您的文件使用假 -吸气剂类。你的假程序应该返回一个硬编码的文件列表。如果您使用真实文件获取器和真实文件处理器,您将不知道哪一个导致测试失败。因此,在测试中,您的文件处理器类应该使用假文件获取器类。您的文件处理器类应该采用文件获取器接口。在实际代码中,您将传入真正的文件获取器。在测试代码中,您将传递一个假文件获取器,该文件返回器返回已知的静态列表。
基本原则是:
There isn't necessarily one thing to do when testing the file system. In truth, there are several things you might do, depending on the circumstances.
The question you need to ask is: What am I testing?
That the file system works? You probably don't need to test that unless you're using an operating system which you're extremely unfamiliar with. So if you're simply giving a command to save files, for instance, it's a waste of time to write a test to make sure they really save.
That the files get saved to the right place? Well, how do you know what the right place is? Presumably you have code that combines a path with a file name. This is code you can test easily: Your input is two strings, and your output should be a string which is a valid file location constructed using those two strings.
That you get the right set of files from a directory? You'll probably have to write a test for your file-getter class that really tests the file system. But you should use a test directory with files in it that won't change. You should also put this test in an integration test project, because this is not a true unit test, because it depends on the file system.
But, I need to do something with the files I get. For that test, you should use a fake for your file-getter class. Your fake should return a hard-coded list of files. If you use a real file-getter and a real file-processor, you won't know which one causes a test failure. So your file-processor class, in testing, should make use of a fake file-getter class. Your file-processor class should take the file-getter interface. In real code, you'll pass in the real file-getter. In test code you'll pass a fake file-getter that returns a known, static list.
The fundamental principles are:
查看TDD 教程< /a> 使用 Rhino Mocks 和 SystemWrapper。
SystemWrapper 包装了许多 System.IO 类,包括 File、FileInfo、Directory、DirectoryInfo 等。您可以查看完整列表。
在本教程中,我将展示如何使用 MbUnit 进行测试,但对于 NUnit 来说是完全相同的。
您的测试将如下所示:
Check out Tutorial to TDD using Rhino Mocks and SystemWrapper.
SystemWrapper wraps many of System.IO classes including File, FileInfo, Directory, DirectoryInfo, ... . You can see the complete list.
In this tutorial I'm showing how to do testing with MbUnit but it's exactly the same for NUnit.
Your test is going to look something like this:
Q1:
这里有三个选择。
选项 1:接受它。
(没有示例:P)
选项 2:在需要时创建一个轻微的抽象。
而不是执行文件 I/O(File.ReadAllBytes 或无论如何)在被测试的方法中,您可以更改它,以便在外部完成 IO 并传递流。
会变成
这种方法是一种权衡。首先,是的,它更易于测试。然而,它以可测试性为代价换取了稍微增加的复杂性。这可能会影响可维护性和您必须编写的代码量,而且您可能只是将测试问题提升一级。
然而,根据我的经验,这是一种很好的、平衡的方法,因为您可以概括并使其可测试重要的逻辑,而无需致力于完全包装的文件系统。也就是说,您可以概括您真正关心的部分,而其余部分保持原样。
选项 3:包装整个文件系统
更进一步,模拟文件系统可能是一种有效的方法;这取决于你愿意忍受多少肿胀。
我以前也走过这条路;我有一个包装的文件系统实现,但最后我只是删除了它。 API 之间存在细微的差异,我必须将它注入到任何地方,最终这是额外的痛苦却收效甚微,因为许多使用它的类对我来说并不是非常重要。不过,如果我一直在使用 IoC 容器或编写一些关键的东西并且测试需要快速,我可能会坚持使用它。与所有这些选项一样,您的里程可能会有所不同。
至于你的 IoC 容器问题:
手动注入你的测试双打。如果您必须做大量重复性工作,只需在测试中使用设置/工厂方法即可。使用 IoC 容器进行测试实在是太过分了!不过,也许我不明白你的第二个问题。
Q1:
You have three options here.
Option 1: Live with it.
(no example :P)
Option 2: Create a slight abstraction where required.
Instead of doing the file I/O (File.ReadAllBytes or whatever) in the method under test, you could change it so that the IO is done outside and a stream is passed instead.
would become
This approach is a tradeoff. Firstly, yes, it is more testable. However, it trades testability for a slight addition to complexity. This can hit maintainability and the amount of code you have to write, plus you may just move your testing problem up one level.
However, in my experience this is a nice, balanced approach as you can generalise and make testable the important logic without committing yourself to a fully wrapped file system. I.e. you can generalise the bits you really care about, while leaving the rest as is.
Option 3: Wrap the whole file system
Taking it a step further, mocking the filesystem can be a valid approach; it depends on how much bloat you're willing to live with.
I've gone this route before; I had a wrapped file system implementation, but in the end I just deleted it. There were subtle differences in the API, I had to inject it everywhere and ultimately it was extra pain for little gain as many of the classes using it weren't hugely important to me. If I had been using an IoC container or writing something that was critical and the tests needed to be fast I might have stuck with it, though. As with all of these options, your mileage may vary.
As for your IoC container question:
Inject your test doubles manually. If you have to do a lot of repetitive work, just use setup/factory methods in your tests. Using an IoC container for testing would be overkill in the extreme! Maybe I am not understanding your second question, though.