Modular file input / output

Looking through existing tags related to unit testing, here in Stack Overflow, I couldn’t find a definite answer on how to perform input / output operations of unit test files. I only recently began to study unit testing, having previously learned about the benefits, but with difficulty got used to writing tests in the first place. I created my project to use NUnit and Rhino Mocks, and although I understand the concept behind them, I have a little problem understanding how to use Mock Objects.

In particular, I have two questions that I would answer. First, what is the correct way to work with unit test I / O files? Secondly, in my attempts to learn about unit testing, I came across dependency injection. After creating and working with Ninject, I was wondering if I should use DI inside my unit tests or just create objects directly.

+49
c # dependency-injection file-io unit-testing nunit
Oct. 06 '09 at 21:14
source share
4 answers

Check out the TDD Tutorial using Rhino Mocks and SystemWrapper .

SystemWrapper wraps many of the System.IO classes, including File, FileInfo, Directory, DirectoryInfo, .... You can see the full list .

In this tutorial, I'll show you how to test using MbUnit, but it is exactly the same for NUnit.

Your test will look something like this:

[Test] public void When_try_to_create_directory_that_already_exists_return_false() { var directoryInfoStub = MockRepository.GenerateStub<IDirectoryInfoWrap>(); directoryInfoStub.Stub(x => x.Exists).Return(true); Assert.AreEqual(false, new DirectoryInfoSample().TryToCreateDirectory(directoryInfoStub)); directoryInfoStub.AssertWasNotCalled(x => x.Create()); } 
+28
Oct 06 '09 at 21:18
source share

When testing a file system, it is not necessary to do one thing. In truth, there are a few things you can do, as the case may be.

The question you need to ask is: what am I testing?

  • What does the file system work? You probably do not need to check this if you are not using an operating system that you are very unfamiliar with. Therefore, if you just give a command to save files, for example, it is a waste of time to write a test to make sure that they are really saved.

  • To keep the files in the right place? Well, how do you know what a suitable place is? Presumably you have code that combines a path with a file name. This is code that you can easily test: your input consists of two lines, and your output should be a line, which is a valid file location constructed using these two lines.

  • What do you get the right set of files from the directory? You will probably have to write a test for your class with a file getter that really checks the file system. But you should use a test directory with files in it that will not change. You should also put this test into an integration project because it is not a true unit test, because it depends on the file system.

  • But I need to do something with the files that I get. For this test, you must use a fake for your receiver class. Your fake should return a hard list of files. If you use a real file hosting service and a real file processor, you won’t know which one causes the test to fail. Thus, your file processor class should use a fake file getter class when testing. Your file processor class should use the file-getter interface. In real code, you will be taken to a real destination file. In the test code, you will pass a fake recipient file that returns a known static list.

Basic principles:

  • Use the fake file system hidden behind the interface when you are not testing the file system itself.
  • If you need to test real file operations,
    • mark the test as an integration test, not a unit test.
    • have an assigned test directory, a set of files, etc., which will always be in an unchanged state, so your test integration tests with files can take place sequentially.
+31
Nov 07 2018-11-11T00:
source share

Q1:

You have three options.

Option 1: live with it.

(no example: P)

Option 2: If necessary, create a small abstraction.

Instead of executing the I / O file (File.ReadAllBytes or something else) in the test method, you can change it so that IO runs outside and a stream is passed instead.

 public class MyClassThatOpensFiles { public bool IsDataValid(string filename) { var filebytes = File.ReadAllBytes(filename); DoSomethingWithFile(fileBytes); } } 

will become

 // File IO is done outside prior to this call, so in the level // above the caller would open a file and pass in the stream public class MyClassThatNoLongerOpensFiles { public bool IsDataValid(Stream stream) // or byte[] { DoSomethingWithStreamInstead(stream); // can be a memorystream in tests } } 

This approach is a compromise. Firstly, yes, this is more verifiable. However, he trades in testability for a small addition to complexity. This can affect maintainability and the amount of code you have to write, plus you can simply move your test problem one level.

However, in my experience, this is a good balanced approach, since you can generalize and make important test logic without having to go over yourself with a fully packed file system. That is, you can generalize the bits that you are really interested in, leaving everything else as it is.

Option 3: Wrap the entire file system

Taking another step, mocking the file system may be a valid approach; it depends on how much swelling you are ready to live.

I went this way 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 introduce them everywhere, and in the end it was an extra pain for a small gain, since many of the classes using it were not very important to me. If I used an IoC container or wrote something critical and the tests were supposed to be fast, I could be stuck with it. As with all of these options, your mileage may vary.

Regarding your question about the IoC container:

Enter test doubling manually. If you need to perform many repetitive actions, just use the / factory installation methods in your tests. Using an IoC container for testing would be overkill! Perhaps I do not understand your second question.

+9
Oct 06 '09 at 21:37
source share

I am currently using the IFileSystem object through dependency injection. For production code, the wrapper class implements the interface, wrapping the specific I / O functions that I need. When testing, I can create a null or stub and provide it to the tested class. A proven class is no more wise.

+1
Oct. 06 '09 at 23:12
source share



All Articles