Home > Uncategorized > TuneWiz Loosely Coupled, Easily Testable

TuneWiz Loosely Coupled, Easily Testable

January 13th, 2009 Leave a comment Go to comments

A few years ago someone introduced me to the idea of unit testing with NUnit.  Brilliant!  Whenever I develop applications I inevitably write a lot of throw away code.  Lots of code to verify assumptions.  I would simply delete or comment out this code.  With unit testing, I keep all this code and I re-run it to verify any changes I’ve made haven’t invalidated my assumptions.  A while later, another light bulb lit up for me.  These ‘unit tests’ I’d been writing were not unit tests at all!  They were integration tests.  Sure I would test the class in question, but I would create any supporting objects along the way.  At first this double testing of objects seemed like over-engineering, but at some point I realized I was creating a lot of redundency.  Introduce a database into the equation and now I’m doing more testing than necessary and my tests are running slowly because I’m making round trips to a database.   If I want to test business logic, it makes sense that I would want to separate this from testing whether or not I can read and write to a database.

I’m still learning proper unit testing.  While not specific to unit testing, there is in the community at large,  the helpful concept of dependency injection or inversion of control.  One aspect I’ve latched onto is instead of creating ‘new’ objects in a class, the class should take these objects as parameters passed in to your constructor or methods.  Your methods or constructors expect an interface and you leave it up the caller what implementation you want to push in.   Even if you only have one concrete implementation of your interface, this leaves the door open to to create a fake implementation for your unit tests.  Or you can use a mocking framework like Moq or Rhino Mocks to help you do this.   So what this helps you to do is create code that tests a single unit of functionaltiy in isolation.

So now that we have the background out of the way, let’s get to how this applies to TuneWiz.

I have an object called TuneTrack that contains your basic track information.  I created a TuneTrack collection to hold a bunch of these tracks.  I decided to add a GetOrphanTracks method to this collection so I could get all the tracks that iTunes lists in it’s library, but don’t exist on disk.   I can tell a track is an orphan if the Location property of it is null or if it doesn’t exist on disk.  Simple enough. Here’s my first unit test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Test]
public void Return_orphan_track_with_location_null()
{
	// Mock TuneTrack created with Moq library
	var mock = new Mock<TuneTrack>(null);
	// Compiler doesnt like it when we simply pass in bare null for Returns
	string nullStr = null;
	// When something accesses our mock implementation's Locaion
	// property, we make it return null
	mock.Setup(x => x.Location).Returns(nullStr);
 
	// Create the TuneCache object we're unit testing
	var tc = new TuneCache();
	// Add the mock TuneTrack to the collection
	tc.Add(mock.Object);
 
	// Make sure we have a 'real' tunetrack
	Assert.IsNotNull(tc.GetOrphanTracks().First());
	// Location of track should be null as reported by our mock object
	Assert.IsNull(tc.GetOrphanTracks().First().Location);
}

If a TuneTrack’s Location property is null, that means it definitely doesn’t exist on disk. Excellent.

Here’s the method in the TuneCache object that makes that test pass:

1
2
3
4
5
6
7
8
9
10
public IEnumerable<TuneTrack> GetOrphanTracks()
{
	// LINQ statement to return orphan tracks
	// In my experience, iTunes seems to return a null when the file doesnt exist on disk
	var orphanTracks = from t in this
				 where t.Location == null
				 select t;
 
	return orphanTracks;
}

Next test we want to create a mock TuneTrack with a real location that does not exist on disk. Here’s where it gets tricky. The old me that didn’t know the difference between an integration test and a unit test would create a TestData directory, fill it with some files and write some tests against those files. Some calls to our static friend File.Exists and whiz bang we’re done! I remind myself I just want to test the TuneCache object. I don’t want to test the File.Exists method.

This is where that IoC stuff comes in. I did some searching to see what other people do about File.Exists type work in regards to testing. Create an interface and in my concrete implementation create a non-static method that calls File.Exists. That seemed to gel with advice that static methods and singletons might be harmful for testing. So I need to refactor my GetOrphanTracks method to take my new IFileTools interface. The big picture here is that I can swap in a FAKE implementation that can be used to simulate files existing or not existing. Here is my one of my unit tests for this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[Test]
public void Return_one_orphan_track_where_location_does_not_exist()
{
	// Mock TuneTrack created with Moq library
	var mockTune = new Mock<TuneTrack>(null);
	// This file probably exists on most systems
	// it doesn't matter though because it's fake
	// no files are harmed or accessed in this method
	var notepad = "C:\\windows\\notepad.exe";
	// when the location of our mockTune is accessed
	// the location will be notepad
	mockTune.Setup(x => x.Location).Returns(notepad);
 
	// Create our real TuneCache
	var tc = new TuneCache();
	// Add our fake tunetrack
	tc.Add(mockTune.Object);
 
	// Create a fake IFileTools that will simulate
	// a file not existing.  This isolates the test
	// and removes the dependency on the file system and File.Exists()
	var fakeFileTools = new Mock<IFileTools>();
	// any call to exists return false
	fakeFileTools.Setup(x => x.Exists(It.IsAny<string>())).Returns(false);
 
	// Get list of orphan tracks using our mock FileTools that will tell
	// us no file exists
	var orphanTracks = tc.GetOrphanTracks(fakeFileTools.Object); 
	Assert.IsTrue(orphanTracks.Count() == 1, "Cache did not contain 1 orphan track");
	Assert.AreEqual(notepad, orphanTracks.First().Location, "TuneTrack did not have expected location on disk");
}

Now let’s look at the new method to make this test pass:

1
2
3
4
5
6
7
8
9
10
11
public IEnumerable<TuneTrack> GetOrphanTracks(IFileTools fileTools)
{
	// LINQ statement to return orphan tracks
	// In my experience, iTunes seems to return a null when the file doesnt exist on disk
	// I believe iTunes may perform it's own Exist, but we'll be extra safe and do our own
	var orphanTracks = from t in this
				 where t.Location == null || fileTools.Exists(t.Location) == false
				 select t;
 
	return orphanTracks;
}

The one thing that feels awkward to me is now in my real code I have to pass in a new ConcreteFileTools() object when I call my get orphans method. It just feels awkward to me. I know there are tools to help with this, but I’m not ready to pick an IoC library. I don’t feel like things are complex enough to warrant it. I was just read this article, Dependency Injection in the Real World. It gave me an idea:

1
2
3
4
public IEnumerable<TuneTrack> GetOrphanTracks()
{
	return GetOrphanTracks(new FileTools());
}

Bang! Now in the ‘real’ world, I don’t have to think about it. I only worry about injecting an IFileTools in test isolation land. Now if I find I need multiple concrete implementations of IFileTools, I will probably switch my approach. But now my unit tests pass and TuneCache doesn’t feel too encumbered. On the other hand, wasn’t I trying to get away from new? Perhaps this is a a hybrid approach for easing slowly into the IoC world.

I have checked the code in for this. Go take a look at the files for the above implementation.

Categories: Uncategorized Tags: , , , , ,
  1. No comments yet.
  1. No trackbacks yet.