views:

206

answers:

9

When unit testing, it is always hard to know how low down to the framework you go.

If we have a class that directly depends on the .NET Framework, i.e. the System.IO.File class, then we cant really test that in isolation.

Sure we could wrap it and inject it into the dependent class, but then we start to wrap every .NET class! What if that wrapper is not a straight call through? Perhaps it does some checking/business logic first, that you would want to test?

Currently we wrap down to a certain level, and then just don't bother unit testing that wrapper. Perhaps this is ok, because it will be tested later on through integration and exploratory testing?

Here is an example in C# just to illustrate my point:

This class is tightly coupled to the .NET Framework.. thats fine, but now I cant test it in isolation, it requires files to exist etc.

public class PathResolver
{
    public string Resolve(string filename)
    {
        string completePath = string.Empty;
        if(!File.Exists(filename))
        {
            return Path.Combine(@"D:\MyExample", filename);
        }
        return completePath;
    }
}

We can unit test this by doing something like:

public class PathResolver
{
    private readonly IFileSystem _fileSystem;

    public PathResolver(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    public string Resolve(string filename)
    {
        string completePath = string.Empty;
        if(!_fileSystem.Exists(filename))
        {
            return _fileSystem.Combine(@"D:\MyExample", filename);
        }
        return completePath;
    }
}

But now we cant test the "FileSystem" class!

What are other peoples thoughts?

+5  A: 

In general, testing frameworks like .NET is not your responsibility, it's the responsibility of the people releasing the framework.

Amber
+1. You don't need to test what has already been tested unless you suspect a bug in framework code, which is highly unlikely.
RichardOD
Yeah I agree with that, but I am not trying to test the framework.. I am testing to ensure my code calls it, not that it performs it's job correctly!
David Kiff
Frameworks present an interesting case because they effectively turn certain things that would be unit testing in a from-scratch app into integration testing (since you're now using a "unit" from the framework instead of your own code).
Amber
Yes, which is the exact purpose of my question! The issue is that we couple .NET so closely with our apps (which is fine), although this stops alot of classes from being unit testable...i.e. classes that interact with the .NET message queue, database, file system classes etc.
David Kiff
A: 

Do you want to test System.IO ? If not, why not to use mocking/stubing approach to get rid of dependency?

Ray
Yeah, thats the example I showed... and hence the question.. is it worth mocking/stubbing (i.e. wrapping) all the .NET classes to ensure our code interacts with it?
David Kiff
Yeah, generally speaking you want to test only your code. And more than that if you testing a method of class A which is depended on method of class B then you really should mock class B, so you only test logic of the class A method (well, there is special cases when you test interection between modules - its called integration tests)
Ray
Sorry, I said yes to the using a mocking framework... I do not want to test System.IO! The problem with mocking is NMock2 only supports interfaces, so we need an interface for File (which we dont have).. therefore we need to wrap it and inject that in... however this means we start to wrap loads of .NET classes, which feels a bit wrong... which is my question.. do we wrap everything?, leave it untested?, or is there a different approach?
David Kiff
Try RhinoMocks instead of NMock2
Ray
Yeah, we are using that for our new project... we cant change hundreds of lines of code in existing apps though! :) Can RhinoMocks mock concrete classes that have not declared their methods as virtual?
David Kiff
A: 

If you use dependency injection you can ensure that your class's external calls are directed towards a stub, rather than an external library.

Visage
Yeah, thats what the second example shows in my question...!
David Kiff
A: 

Unfortunately you can't test everything in an application when unit testing. This is why when testing you aim to have your tests do a 90% code coverage over the project.

Do this test as part of your integration testing or even part of your end-to-end testing because the action of testing this will be rather expensive

AutomatedTester
+1  A: 

It's not necessary to stub out your dependencies on the whole framework - just those parts that hamper your ability to unit test your code. So I think you should feel free to stub out System.IO.File operations, along with database calls. I also find it very useful to have an IClock interface to tell the time instead of calling DateTime.UtcNow - this enables us to control the passage of time in unit tests.

Matt Howells
Yeah this was my initial approach, however there seems to be more and more things to wrap, message queues, file systems, databases, dates, the Random class etc... im starting to feel like im wrapping loads of classes!...hmm
David Kiff
+1  A: 

Indeed, at some point you'll have to implement interfaces like IFileSystem, thereby glueing your application to the .NET framework. This "glue-code-in-between" cannot be unit tested. This is not much of a problem as long as the glue code is very simple.

If the glue code is not as simple as you'd like, you can still do automated integration tests, e.g. run your assembled application in some sort of self-test mode.

The reason unit tests are more popular is that they are simpler to create and less fragile. You don't need to prepare a database, web server or a file system in order to run a unit test. You don't need to worry about the unit tests for class A breaking if you change class B. Integration tests can test more things but they don't have these advantages.

Wim Coenen
Yeah I think your right, just keep the glue code as simple as possible, and if it is looking a little complex, allow our alternate testing methods to pick it up. It just slightly reduces the value of having unit tests I guess... we wont know if we have broken something in one of those classes whilst maintaining it!
David Kiff
A: 

You only need to test your class, not the framework below.

For this you need a testdriver that sets up a testcase, in your example create a file to resolve, instantiate your class and let it do its work.

A good testdriver cleans up the test scenario as well and leaves the system in the same state as before the test.

BeowulfOF
I know! I am not trying to test the framework! I want it to be issolated, however that comes at the cost of wrapping loads of .NET classes... which is my point.
David Kiff
Your class is isolated when it does not use any other class of your project. Theres no need to isolate it from the framework. You test the input to your class, and the result of your class, if thats wrong, something in you class, calling the framework might be wrong - but it is indicated through this test already.
BeowulfOF
"Your class is isolated when it does not use any other class of your project" Im not sure this is a complete definition? - I would consider a test issolated if it also does not interact with the file system, database, message queues etc as well. In that case the File class is interacting with the file system... not issolating my test?
David Kiff
The File class is not to be testet by you, since it is part of the framework. The communication between the framework and the filesystem is out of scope, too. Technically you could and ideologists would test this, but this is far more testing than can be offered by sane programmers.
BeowulfOF
Im not testing the file class!! I am testing the Path resolver class!!If I test the classes "Resolve" method, it will interact with the file system. My second example, will not, because I have wrapped the File class, however then I am wrapping everything..
David Kiff
Exactly that did i meant, its just not sane to wrap (mock) everything just to be able to test things, that have been testet for the framework already.
BeowulfOF
A: 

If you can't test a class, but you can mock it using an Isolation Framework (Rhino Mocks, Typemock, Moq), you can fake out the details of that "untestable" class.

Chris Missal
+1  A: 

This is a fine example of difference between unit testing and integration testing. For unit testing your PathResolver, you need to pass a mock object created by hand or using a mock framework (such as my favorite Moq). Using a mock object, you'll able to test if Combine method was called.

The unit test will look something like this (using Moq):

[Test]
public void ShouldCombinePath()
{
    IFileSystem fs = new Mock<IFileSystem>();

    PathResolver resolver = new PathResolver(fs.Object);

    resolver.Resolve("Test.filename");

    fs.Verify(fs => fs.Combine());
}

Unit test are supposed to be executed fast without external dependencies. They should be called on every compile.

But you're right, you still need to test the concrete class. This is what we call integration testing. I suggest you create a separate project called "MyProject.IntegrationTest" and test SystemFileSystem directly using test files included in the project.

The integration test should look like this

[Test]
public void ShouldCombinePath()
{
    DotNetFileSystem fileSystem = new DotNetFileSystem();

    string resultPath = fileSystem.Combine("Test.filename");

    Assert.That(resultPath, Text.Contains("@D:\MyExample\Test.filename"));
}

Integration tests are usually called when creating a build of the software on a new commit, using a continuous integration software. They can be slow because they use external dependencies.

Michaël Larouche
So essentially you are agreeing with my current approach, and leaving that untested code to other testing such as integration/exploratory?
David Kiff
Test the concrete implementation when you will need it. Like I suggested, use a different project because these kinds of test took time.In my current project, my NHibernate integration test take about 1 minute to run. I integrated NHibernate late in my development and the tests really helped find the problem faster than if I launched the site and use the UI to test it. It's worth it.
Michaël Larouche