More and more people are talking about unit testing, and I’m glad. There is confusion, however, with unit testing and integration testing, and some folks don’t know there is a difference. Unit testing is testing code in isolation without exercising code not under test. Simply put, if I have a class that handles data that is stored in a file, I want to test my custom class without actually exercising the file system. How do I do that? Why do I want to do that. I’ll tackle the why first. If a test requires that an actual file exists, then you have a pretty significant dependency in your code. Your code can never run (can never be tested) except by working with the actual file on a file system configured with the correct permissions. This is fraught with environmental headaches. Unit tests should be simple and fast. The unit tests should pass everywhere, not just on one magic environment where everything is painstakingly set up.
Here’s how: Consider the following code with a test:
1 using NUnit.Framework;
2
3 namespace DIPSample
4 {
5 [TestFixture]
6 public class DataManagerTester
7 {
8 [Test]
9 public void SaveDate()
10 {
11 DataManager manager = new DataManager();
12 manager.AddData(“Some string data”);
13 manager.SaveData();
14 }
15 }
16 }
1 using System;
2 using System.IO;
3
4 namespace DIPSample
5 {
6 class DataManager
7 {
8 private string _data;
9
10 public void AddData(string data)
11 {
12 _data = data;
13 }
14
15 public void SaveData()
16 {
17 string path = @”C:dataFile.dat”;
18 if(!File.Exists(path))
19 throw new InvalidOperationException(“Data storage isn’t ready.”);
20
21 //Do something to maintain this data file. . .
22 //Add info to file, replace data in file, etc. Use your imagination.
23 }
24 }
25 }
This test seeks to verify that SaveData() does what is is supposed to do. The problem is that if our file isn’t available the code will fail. I’m not in a position to configure the environment so that the file is available. I don’t want to mess with the actual file at this point either. I’ll need to modify the code so that it’s testable by itself. The following is the same code modified to be more flexible so that the unit test _is actually a unit test_. The above test is actually an integration test because its running requires the file system to be on the testing stack.
I define an interface to separate my file system dependency:
1 namespace DIPSample
2 {
3 public interface IDataFileStore
4 {
5 void SaveData(string data);
6 }
7 }
I make my concrete implementation that calls the file system. This will be my class for integration and acceptance tests.
1 using System;
2 using System.IO;
3
4 namespace DIPSample
5 {
6 public class FileStore : IDataFileStore
7 {
8 public void SaveData(string data)
9 {
10 string path = @”C:dataFile.dat”;
11 if(!File.Exists(path))
12 throw new InvalidOperationException(“Data storage isn’t ready.”);
13
14 //Do something to maintain this data file. . .
15 //Add info to file, replace data in file, etc. Use your imagination.
16 }
17 }
18 }
Here is how my DataManager class changes. Notice the default constructor and a special constructor specifically for testing:
1 namespace DIPSample
2 {
3 class DataManager
4 {
5 private string _data;
6 private IDataFileStore _fileStore;
7
8 public DataManager()
9 {
10 _fileStore = new FileStore();
11 }
12
13 public DataManager(IDataFileStore _store)
14 {
15 _fileStore = _store;
16 }
17
18 public void AddData(string data)
19 {
20 _data = data;
21 }
22
23 public void SaveData()
24 {
25 _fileStore.SaveData(_data);
26 }
27 }
28 }
And in my test below, I use NMock to mock the interface. It also allows me to verify that the proper method is called with the right data on my interface:
1 using NUnit.Framework;
2 using NMock;
3
4 namespace DIPSample
5 {
6 [TestFixture]
7 public class DataManagerTester
8 {
9 [Test]
10 public void SaveDate()
11 {
12 IMock mock = new DynamicMock(typeof(IDataFileStore));
13 mock.Expect(“SaveData”, “Some string data”);
14
15 DataManager manager = new DataManager((IDataFileStore)mock.MockInstance);
16 manager.AddData(“Some string data”);
17 manager.SaveData();
18
19 //Verifies SaveData method in the mock is actually called with the right input.
20 mock.Verify();
21 }
22 }
23 }
This is called Dependency Injection. You are injecting a dependency to facilitate unit testing. This decouples your code, and it is now more flexible. The above is very important because if you can’t do this, then it will be VERY difficult, nay, impossible to do unit testing on a system that uses: files, a database, queues, web services, remoting, {shudder} DCOM, ASP.NET.