TDD with file system dependencies

I have an integration test LoadFile_DataLoaded_Successfully () . And I want to reorganize it to unit test to break file dependency.

PS I'm new to TDD:

Here is my production class:

public class LocalizationData { private bool IsValidFileName(string fileName) { if (fileName.ToLower().EndsWith("xml")) { return true; } return false; } public XmlDataProvider LoadFile(string fileName) { if (IsValidFileName(fileName)) { XmlDataProvider provider = new XmlDataProvider { IsAsynchronous = false, Source = new Uri(fileName, UriKind.Absolute) }; return provider; } return null; } } 

and my test class (Nunit)

 [TestFixture] class LocalizationDataTest { [Test] public void LoadFile_DataLoaded_Successfully() { var data = new LocalizationData(); string fileName = "d:/azeri.xml"; XmlDataProvider result = data.LoadFile(fileName); Assert.IsNotNull(result); Assert.That(result.Document, Is.Not.Null); } } 

Any idea how to reorganize it to break file system dependency

+6
c # tdd nunit rhino-mocks
source share
11 answers

What you are missing is the inverse of the control. For example, you can introduce the principle of entering dependencies in your code:

 public interface IXmlDataProviderFactory { XmlDataProvider Create(string fileName); } public class LocalizationData { private IXmlDataProviderFactory factory; public LocalizationData(IXmlDataProviderFactory factory) { this.factory = factory; } private bool IsValidFileName(string fileName) { return fileName.ToLower().EndsWith("xml"); } public XmlDataProvider LoadFile(string fileName) { if (IsValidFileName(fileName)) { XmlDataProvider provider = this.factory.Create(fileName); provider.IsAsynchronous = false; return provider; } return null; } } 

In the above code, XmlDataProvider creation XmlDataProvider abstracted using the IXmlDataProviderFactory interface. An implementation of this interface can be provided in the LocalizationData constructor. Now you can write your unit test as follows:

 [Test] public void LoadFile_DataLoaded_Succefully() { // Arrange var expectedProvider = new XmlDataProvider(); string validFileName = CreateValidFileName(); var data = CreateNewLocalizationData(expectedProvider); // Act var actualProvider = data.LoadFile(validFileName); // Assert Assert.AreEqual(expectedProvider, actualProvider); } private static LocalizationData CreateNewLocalizationData( XmlDataProvider expectedProvider) { return new LocalizationData(FakeXmlDataProviderFactory() { ProviderToReturn = expectedProvider }); } private static string CreateValidFileName() { return "d:/azeri.xml"; } 

FakeXmlDataProviderFactory as follows:

 class FakeXmlDataProviderFactory : IXmlDataProviderFactory { public XmlDataProvider ProviderToReturn { get; set; } public XmlDataProvider Create(string fileName) { return this.ProviderToReturn; } } 

Now in a test environment, you can (and probably should) always create a class under the test manually. However, you want to distract the creation in factory methods so that you cannot change many tests when the class under the test changes.

In your work environment, however, this can become very cumbersome very soon when you need to manually create a class. Especially when it contains a lot of dependencies. This is where the IoC / DI frameworks shine. They can help you with this. For example, if you want to use LocalizationData in your production code, you can write code like this:

 var localizer = ServiceLocator.Current.GetInstance<LocalizationData>(); var data = data.LoadFile(fileName); 

Please note that as an example, I am using the Common Service Locator .

The structure will take care of creating this instance for you. However, using such an dependency injection infrastructure, you will need to tell the infrastructure which “services” you need. For example, when I use the Simple Service Locator library as an example (headless plugin), your configuration might look like this:

 var container = new SimpleServiceLocator(); container.RegisterSingle<IXmlDataProviderFactory>( new ProductionXmlDataProviderFactory()); ServiceLocator.SetLocatorProvider(() => container); 

This code is usually included in the path to launch your application. Of course, the only missing piece of the puzzle is the real ProductionXmlDataProviderFactory class. Here he is:

 class ProductionXmlDataProviderFactory : IXmlDataProviderFactory { public XmlDataProvider Create(string fileName) { return new XmlDataProvider { Source = new Uri(fileName, UriKind.Absolute) }; } } 

It also does not follow that you probably do not want to inject your LocalizationData into your production code yourself, because this class is probably used by other classes that depend on this type. What you usually do is ask the framework to create the topmost class for you (for example, a command that implements the full use case) and execute it.

Hope this helps.

+8
source share

The problem is that you are not using TDD. First you wrote production code, and now you want to test it.

Remove all this code and run it again. First write a test, and then write the code that passes this test. Then write the next test, etc.

What is your goal? If the line that ends with "xml" (why not the ".xml"?), You want the XML data provider to be based on a file whose name is that line. Is that your goal?

The first tests would be a degenerate case. For a string such as "name_with_wrong_ending", your function should fail. How can this fail? Should it return null? Or should it be an exception? You can think about it and make a decision in your test. Then you will do a test run.

Now, what about a line like this: "test_file.xml", but in case such a file does not exist? What do you want the function to execute in this case? Should it return null? Should this be an exception?

The easiest way to verify this, of course, is to actually run the code in a directory that does not have this file. However, if you prefer to write a test so that it does not use the file system (a wise choice), then you need to ask the question "does this file exist", and then your test should force the answer to be "false".

You can do this by creating a new method in your class with the name "isFilePresent" or "doFileExist". Your test may override this function to return false. And now you can verify that your LoadFile function is working correctly when the file does not exist.

Of course, now you will need to verify that the normal implementation of "isFilePresent" is working correctly. And for this you will have to use a real file system. However, you can run file system tests from your LocalizationData tests by creating a new class called FileSystem and moving your isFilePresent method to this new class. Then your LocalizationData test can derive from this new FileSystem class and override 'isFilePresent' to return false.

You still have to test the normal implementation of FileSystem, but this is in a different set of tests that run only once.

Ok, what is the next test? What does your loadFile function do when the file exists but does not contain valid xml? Should he do anything? Or is this a problem for the client? You decide. But if you decide to test this, you can use the same strategy as before. Create a function called isValidXML and test it to return false.

Finally, we need to write a test that actually returns an XMLDataProvider. Thus, the final function that 'loadData' should call after all these functions have been created is createXmlDataProvider. And you can override this to return an empty or dummy XmlDataProvider.

Please note that in your tests you never ended up in a real file system and actually created an XMLDataProvider based on the file. But you did a test of every if statement in your loadData function. You have tested the loadData function.

You should now write another test. A test that uses a real file system and a real valid XML file.

+6
source share

When I look at the following code:

 public class LocalizationData { private static bool IsXML(string fileName) { return (fileName != null && fileName.ToLower().EndsWith("xml")); } public XmlDataProvider LoadFile(string fileName) { if (!IsXML(fileName)) return null*; return new XmlDataProvider{ IsAsynchronous = false, Source = new Uri(fileName, UriKind.Absolute) }; } } 
  • (* I am not happy with the return of null.

In any case, I asked myself the following questions:

  • What could break with this code? Is there some kind of complex logic or fragile code that I have to defend myself with?
  • Is there anything complicated to understand or is it worth highlighting through a test that the code cannot communicate?
  • As soon as I write this code, how often do I think I review it? (/)

The IsXML function is extremely trivial. He probably does not even belong to this class.

The LoadFile function creates a synchronous XmlDataProvide if it receives a valid XML file name.

First, I would look for who uses LoadFile and where fileName is coming from. If it is external to our program, we need some verification. If its internal and somewhere else we are already doing a check, then we are good to go. As Martin suggested, I would recommend reorganizing this so that instead of a parameter, enter Uri as a parameter.

As soon as we turn to this, then all we need to know is if there is some special reason why XMLDataProvider is in synchronous mode.

Now, is it worth it to test? The XMLDataProvider is not the class we built, we expect it to work fine when we give a valid Uri.

So, frankly, I would not waste my time writing a test for this. In the future, if we see more logic, we can return to this again.

+3
source share

In one of my (Python) projects, I assume that all unit tests are run in a special directory that contains the “data” (input files) and “output” (output files) folders. I use a test script that first checks to see if these folders exist (i.e. if the current working directory is correct) and then runs the tests. Then my unit tests can use relative file names such as "data / test-input.txt".

I don’t know how to do this in C #, but maybe you can check the presence of the file “data / azeri.xml” in the test SetUp method.

+2
source share

This has nothing to do with your testing (x), but consider using Uri instead of String as the parameter type for your API.

http://msdn.microsoft.com/en-us/library/system.uri(v=VS.100).aspx

x: I think Stephen did a very good job on this topic.

+2
source share

Why are you using XmlDataProvider? I do not think this is a valuable unit test, as it is now. Instead, why don't you check what you would do with this data provider?

For example, if you use XML data to load a list of Foo objects, create an interface:

 public interface IFooLoader { IEnumerable<Foo> LoadFromFile(string fileName); } 

Then you can test your implementation of this class using the test file that you create during the unit test. Thus, you can break your dependency on the file system. Delete the file when your test exits (in the finally block).

As for the co-authors who use this type, you can go to the mock version. You can either pass the layout code or use a mocking structure like Moq, Rhino, TypeMock or NMock. Mocking is great, but if you are new to TDD, then it’s very convenient for you to copy your layouts until you know what they are useful for. Once you succeed, you can find the good, the bad, and the ugly of mocking frameworks. They can be a little rude to work when you start TDD. Your mileage may vary.

Good luck.

+1
source share

In this case, you are mostly at a lower level of dependency. You are testing that a file exists and that xmlprovider can be created with the file as a source.

The only way you could break the dependency is to add something to create an XmlDataProvider . You can then mock it to return the XmlDataProvider you created (as opposed to reading). A simplified example is:

 class XmlDataProviderFactory { public virtual XmlDataProvider NewXmlDataProvider(string fileName) { return new XmlDataProvider { IsAsynchronous = false, Source = new Uri(fileName, UriKind.Absolute) }; } class XmlDataProviderFactoryMock : XmlDataProviderFactory { public override XmlDataProvider NewXmlDataProvider(string fileName) { return new XmlDataProvider(); } } public class LocalizationData { ... public XmlDataProvider LoadFile(string fileName, XmlDataProviderFactory factory) { if (IsValidFileName(fileName)) { return factory.NewXmlDataProvider(fileName); } return null; } } [TestFixture] class LocalizationDataTest { [Test] public void LoadFile_DataLoaded_Succefully() { var data = new LocalizationData(); string fileName = "d:/azeri.xml"; XmlDataProvider result = data.LoadFile(fileName, new XmlDataProviderFactoryMock()); Assert.IsNotNull(result); Assert.That(result.Document, Is.Not.Null); } } 

Using an injection framework can simplify calling LoadFile by entering factory in the class constructor or elsewhere.

0
source share

I like @Steven's answer, except that I think it hasn't gone far enough:

 public interface DataProvider { bool IsValidProvider(); void DisableAsynchronousOperation(); } public class XmlDataProvider : DataProvider { private string fName; private bool asynchronousOperation = true; public XmlDataProvider(string fileName) { fName = fileName; } public bool IsValidProvider() { return fName.ToLower().EndsWith("xml"); } public void DisableAsynchronousOperation() { asynchronousOperation = false; } } public class LocalizationData { private DataProvider dataProvider; public LocalizationData(DataProvider provider) { dataProvider = provider; } public DataProvider Load() { if (provider.IsValidProvider()) { provider.DisableAsynchronousOperation(); return provider; } return null; } } 

Without going far enough, I mean that he did not follow Last Possible Responsible Moment . DataProvider as much on the embedded DataProvider class as possible.

One thing I did not do with this code is a unit test disk and mocks. That's why you still check the status of the provider to make sure it is valid .

Another thing is that I tried to remove depending on the fact that LocalizationData knows that the provider is using the file. What if it is a web service or database?

0
source share

So, first of all, let's understand what we need to check. We need to verify that with a valid file name, your LoadFile (fn) method returns XmlDataProvider, otherwise it returns null.

Why is the LoadFile () method hard to test? Because it creates an XmlDataProvider with a URI created from the file name. I did not work much with C #, but I assume that if the file does not actually exist on the system, we will get an Exception. The real problem is that your production LoadFile () method creates something that is hard to fake . Failure to fake this is a problem because we cannot guarantee the existence of a particular file in all test environments without the need to apply implicit recommendations.

So, the solution - we have to fake the collaborators (XmlDataProvider) of the loadFile method. However, if a method creates its collaborators, it cannot fake them, so the method should never create its co-authors.

If the method does not create its co-authors, how does it get them? - in one of two ways:

  • They must be entered into the method.
  • They must be obtained from some factory.

In this case, you do not need to enter a method for XmlDataProvider, since this is exactly what it returns. Therefore, we must get it from the global Factory - XmlDataProviderFactory.

Here is the interesting part. When your code is running during production, Factory should return an XmlDataProvider, and when your code is running in a test environment, Factory should return a fake object.

Now the only part of the puzzle is how to ensure that Factory behaves differently in different environments? One way is to use some properties that have different meanings in both environments, and another way is to set Factory to what it should return. I personally prefer the old way.

Hope this helps.

0
source share

This time, do not try to break your dependency on the file system. This behavior clearly depends on the file system and seems to be at the point of integration with the file system, so check it with the file system.

Now, I’m Bob’s second tip: throw away this code and try running it. This makes a lot of practice, and that’s how I learned to do it. Good luck.

0
source share
  • Instead of returning an XmlDataProvider that links you to a specific technology, hide this implementation detail. It looks like you need the repository role for

    LocalizationData GetLocalizationData (params)

You may have an implementation for this role that internally uses Xml. You will need to write integration tests to verify that the XmlLocalizationDataRepository can read the actual Xml data stores. (Slow).

  • The rest of your code can make fun of GetLocalizationData ()
0
source share

All Articles