views:

107

answers:

8

Hello. I have an integration test LoadFile_DataLoaded_Succefully(). And I want to refactor it to the unit test for breaking dependency with filesytem.

P.S. I am new in TDD:

Here are 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_Succefully()
    {
        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 refactor it to break filesystem dependecy

+2  A: 

In one of my (Python) projects, I assume that all unit tests are run in a special directory that contains the folders "data" (input files) and "output" (output files). I'm using a test script that first checks whether those folders exists (i.e. if the current working directory is correct) and then runs the tests. My unit tests can then use relative filenames like "data/test-input.txt".

I don't know how to do this in C#, but maybe you can test for existence of the file "data/azeri.xml" in the test SetUp method.

AndiDog
It is interesting idead but I think this approach bad for C# application. I think Some stub object must be created
Polaris
@AndiDog: Any test that calls to the Filesystem is *not* a unit test.
Billy ONeal
@Billy ONeal: Anything that tests units (or modules) is a unit test IMO, as long as the input parameters stay the same. If input parameters are too big or a module does file operations (e.g. a module for managing temporary files), then the unit test needs to read or write files.
AndiDog
A: 

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

The only way that you could break the dependency, would be to inject something to create the XmlDataProvider. You could then mock it to return a XmlDataProvider that you created (as opposed to read). As simplistic example would be:

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 could simplify the call to LoadFile by injecting the factory in the class constructor or elsewhere.

Rod
I agree. If you wanted to unit test this, you'd have to provide the XMLDataProvider from outside the scope of the class instead of newing one up. I think, in this case, you wouldn't be publicly flogged for stopping your test coverage granularity at this narrowly-scoped integration test. Creating the mock just to verify that a particular call is made, especially given that the class is already written, is neither necessary, nor is it really TDD (the test would be written to verify that code you've already written works the way you intended, not to verify code you haven't written yet.
KeithS
@KeithS. I agree. It was more of an example on how to achieve decoupling through the usage of a mock object.
Rod
A: 

Not an answer to your question, but this:

private bool IsValidFileName(string fileName)
{
    if (fileName.ToLower().EndsWith("xml"))
    {
        return true;
    }
    return false;
}

would have identical behavior, and be easier to read, in this form:

private bool IsValidFileName(string fileName)
{
    return (fileName.ToLower().EndsWith("xml"));
}
Carl Manaster
+1  A: 

Why do you use the XmlDataProvider? I don't think that it's a valuable unit test, as it stands now. Instead, why don't you test whatever you would do with that data provider?

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

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

You can then test your implementation of this class using a test file you generate during a unit test. In this way you can break your dependency on the filesystem. Delete the file when your test exits (in a finally block).

And as for collaborators that use this type, you can pass in a mock version. You can either hand code the mock, or use a mocking framework such as Moq, Rhino, TypeMock or NMock. Mocking is great, but if you're new to TDD then it's fine to hand code your mocks while you learn what they're useful for. Once you have that, then you are in a good position to understand the good, bad and ugly of mocking frameworks. They can be a bit gnarly to work with when you're starting TDD. Your mileage may vary.

Best of luck.

Drew Noakes
A: 

My thoughts

  1. Your Class LocalizationData is can not be tested in isolation since it needs its dependency of XMLDataProvider.

  2. If you need to change LoadFile() method which uses some other Loader (i.e. Database, webservice), you can not do this with out modifying your LocalizationData class which means it is violating the rule of Open Closed Principle.

  3. Easy refacor technique is to give some buddy else to manage your class dependencies i.e. read about Service Locator pattern.

  4. Use Some Dependency Injector like (Unity).

More you can find it here http://martinfowler.com/articles/injection.html

saurabh
+6  A: 

What you're missing here is inversion of control. For instance, you can introduce the dependency injection principle into 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 code above the creation of the XmlDataProvider is abstracted away using an IXmlDataProviderFactory interface. An implementation of that interface can be supplied in the constructor of the LocalizationData. You can now 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";
}

The FakeXmlDataProviderFactory looks like this:

class FakeXmlDataProviderFactory : IXmlDataProviderFactory
{
    public XmlDataProvider ProviderToReturn { get; set; }

    public XmlDataProvider Create(string fileName)
    {
        return this.ProviderToReturn;
    }
}

Now in your test environment you can (and probably should) always create the class under test manually. However, you want to abstract the creation away in factory methods to prevent you having to change many tests when the class under test changes.

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

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

var data = data.LoadFile(fileName);

Note that I'm using the Common Service Locator as an example here.

The framework will take care of the creation of that instance for you. Using such a dependency injection framework however, you will have to let the framework know which 'services' your application needs. For instance, when I use the Simple Service Locator library as an example (shameless plug that is), your configuration might look like this:

var container = new SimpleServiceLocator();

container.RegisterSingle<IXmlDataProviderFactory>(
    new ProductionXmlDataProviderFactory());

ServiceLocator.SetLocatorProvider(() => container);

This code will usually go in the startup path of your application. Of course the only missing piece of the puzzle is the actual ProductionXmlDataProviderFactory class. Here is it:

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

Please also not that you will probably don't want to new up your LocalizationData in your production code yourself, because this class is probably used by other classes that depend on this type. What you would normally do is ask the framework to create the top most class for you (for instance the command that implements a complete use case) and execute it.

I hope this helps.

Steven
A: 

I Like @Steven's answer except I think He didn't go 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;
    }
}

By not going far enough I mean that he didn't follow the Last Possible Responsible Moment. Push as much down into the implemented DataProvider class as possible.

One thing I didn't do with this code, is drive it with unit tests and mocks. That is why you're still checking the state of the provider to see if it is valid.

Another thing is that I tried to remove the dependencies on having the LocalizationData know that the provider is using a file. What if it was a web service or database?

Gutzofter
A: 

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

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

x: I think Steven covered that topic pretty very well.

Martin R-L