5

Technology Stack: .NET 4, C#, NUnit

I am attempting to apply test driven development to a new project that performs image processing. I have a base class that contains shared file I/O methods and subclasses that perform various specific processing algorithms. As I understand it, unit tests do not touch the file system or other objects, and mock behavior where that occurs. My base class only contains simple accessors and straightforward file system I/O calls.

public class BaseFile
{
    public String Path { get; set; }

    public BaseFile()
    {
        Path = String.Empty;
    }

    public BaseFile(String path)
    {
        if (!File.Exists(path))
        {
            throw new FileNotFoundException("File not found.", path);
        }

        Path = path;
    }
}

Is there any value in testing these methods? If so, how could I abstract away the calls to the file system?

My other question is how to test the subclass which is specific to a type of image file (~200 MB). I have searched the site and found similar questions, but none dealing with the file sizes I am working with on this project. Is it plausible for a class to have integration tests (using a "golden file"), but no unit tests? How could I strictly follow TDD methods and first write a failing test in this case?

Community
  • 1
  • 1
Noren
  • 793
  • 3
  • 10
  • 23
  • 4
    If TDD is difficult to apply, or inadequate, don't apply it. It's not silver bullet. – CharlesB Feb 15 '12 at 16:56
  • 1
    @CharlesB, I agree. Unfortunately that sentiment is often used as as excuse for not using TDD when it's actually right and advantageous to do so. Sometimes there is big curve or a lot of groundwork to do but this usually pays off. – Myles McDonnell Feb 15 '12 at 16:59
  • 1
    @CharlesB Not sure I agree with that. The problem is that then this class which does some work is not tested, and thus your confidence to change it is diminished. The problem is that its not easy to mock the static methods in System.IO, but that doesn't mean give up on testing, it just means you need to do a little more work to make them testable. This is why MS introduces System.Web.HttpContextBase, to address the issues with not easily being able to mock an HttpContext. We don't need to test httpContext, just that our code correctly interacts with it. – Andy Feb 15 '12 at 17:06
  • 4
    @CharlesB - TDD is a discipline. It's not really intended to solve any one problem. What it does is force you to apply clean boundaries to your code, and decouple yourself from implementation details. As a side effect... you get a set of tests. – Josh Feb 15 '12 at 17:10

5 Answers5

4

In answer to your first question, yes there is value in testing these methods. I have published a library that facilitates doing exactly that without actually hitting the file system: https://bitbucket.org/mylesmcdonnell/mpm.io/wiki/Home

In (not an) answer to your second question I would need to see some code, but I suspect you may need to take a similar approach to the above lib. namely; define interface, define proxy to concrete, define factory to return proxy or mock.

Myles McDonnell
  • 12,943
  • 17
  • 66
  • 116
  • Thank you for posting this source code. I'm marking your post as the answer because you provided the best general solution. – Noren Feb 15 '12 at 22:30
3

How could I strictly follow TDD methods and first write a failing test in this case?

Easy! You mock the file system :)

This may sound like a lot of work, but typically you only need to implement a few methods, and expand as necessary. In the case above... you only need one.

public interface IFileStore
{
    Boolean FileExists(String path);
}

The idea is to delegate your file work behind the interface, and create a concrete implementation to do the heavy lifting. Basically an Adapter pattern.

I don't even object to "poor man's DI" for this kind of thing as you can implement a container later if your application calls for it. In this case... you are probably always going to be using the real file system.

public class BaseFile
{
    private IFileStore _fileStore
    public IFileStore FileStore
    {
        get
        {
            return _fileStore ?? (_fileStore = new ConcreteFileStore());
        }
        set
        {
            _fileStore = value;
        }
    }

    //SNIP...
}

Now you have a testable implementation, and you won't have to rely on any "Golden" files.

Josh
  • 44,706
  • 7
  • 102
  • 124
  • Thank you for your post. For now, I am going to implement your solution until I need more of the System.IO calls, at which point I'll use Myles's code. – Noren Feb 15 '12 at 22:32
1

There is value in testing these methods

While it may seem like extra work for trivial gain right now, adding tests like BaseFile should throw FileNotFoundException when the file does not exist accomplishes at least two goals:

Define a list of expected behaviors

Newcomers to the project can review the test names to determine how your classes are intended to operate. They will know what to expect in each situation -- an exception, a null, a default result, etc.

It also forces you to think through and define in plain English how you want things to operate, as opposed to just throwing in conditions and exceptions here and there. This should result in a very consistent philosophy applied across your project.

Grow a suite of automated regression tests

Consider that someone sees some code throwing an exception in a particular condition, but they think it is wiser to do something else (eat the error, but add a new IsValid property so that consumers can know whether the construction / initialization was successful). If they make such a change, the test will very rapidly draw attention to the change. There was a conscious and intentional decision behind the way things were, and people might have grown to rely on the existing behavior -- this change needs further discussion before it can be accepted.

As for the second part of your question, I think Josh and Myles have both provided sound advice already.

David Ruttka
  • 14,269
  • 2
  • 44
  • 39
1

Mocking simple file system calls using interfaces seems like an overkill. Same goes for things like mocking current time using ITimeService. I tend to use Func or Action because it is so much simpler:

public static Func<string, bool> FileExists = System.IO.File.Exists;
public static Func<DateTime> GetCurrentTime = () => DateTime.Now;

Since those are public I can mock the easily in the unit tests. Code stays simple, no need to inject various interfaces for simple things. For streams I usually use MemoryStream in unit tets.

Toni Parviainen
  • 2,217
  • 1
  • 16
  • 15
0

Integration tests have value on their own. If you mock the file system, like explained in Joshs answer, you really don't know for sure that your code will actually run in production. The file system has a lot of hidden contracts that are non-trivial to mock. If your mock/fake shows slightly different behavior your code might start to rely on it without you knowing.

Only an integration test can tell certain things for sure. (Integration tests have disadvantages as well!).

usr
  • 168,620
  • 35
  • 240
  • 369
  • Correct. I'm not implying that you *shouldn't* do integration test. Rather, that TDD can still be done even on things that we normally don't consider like File Systems. – Josh Feb 15 '12 at 18:17
  • I did not mean do devalue your explanation. I referenced it because it was good. – usr Feb 15 '12 at 21:48