Another way to unit test a class that retrieves information from a file

March 4th, 2009 by ganton | Print

In my previous post I wrote that a unit test that uses a real file on a disk is more like an integration test than as a unit test. Well, I think that I wasn’t completely right because if you write a test that creates a file before the test and after the test your code clears the file it will be a unit test but not an integration test. That’s why I’ve decided to write in this post a sample how to create a test that will do the same work as those test in my previous post but using a file on disk. In addition, you will see how the way of unit testing can affect the real class implementation.

We need the same functionality as before – a class that will read some SQL keywords from a file and will write them into a property named Keywords. I’ll create a property Keywords in the test as before and will check in the test method. The initial test below will not run because we have not any keywords in the collection.

   1: [TestFixture]
   2: public class SqlKeywordsManagerTestFixture1
   3: {
   4:     private string selectKeyword = "SELECT";
   5:     private string insertKeyword = "INSERT";
   6:     private string updateKeyword = "UPDATE";
   7:     private string deleteKeyword = "DELETE";
   8:  
   9:     public Dictionary<string, string> Keywords { get; set; }
  10:  
  11:     [SetUp]
  12:     public void SetUp()
  13:     {
  14:         Keywords = new Dictionary<string, string>();
  15:     }
  16:  
  17:     [Test]
  18:     public void LoadTest()
  19:     {
  20:         Assert.AreEqual(selectKeyword, manager.Keywords[selectKeyword]);
  21:         Assert.AreEqual(insertKeyword, manager.Keywords[insertKeyword]);
  22:         Assert.AreEqual(updateKeyword, manager.Keywords[updateKeyword]);
  23:         Assert.AreEqual(deleteKeyword, manager.Keywords[deleteKeyword]);
  24:     }
  25: }

The next step is to create a file with required data in SetUp method and delete the file in TearDown method of the test fixture. I defined an additional property filePath to store generated file path. It is a good practice to create a file with a unique name in order to prevent other tests to try to use the same file. For this purpose I’ll use a GUID as the file name. The SetUp and TearDown methods are below.

   1: [SetUp]
   2: public void SetUp()
   3: {
   4:     Keywords = new Dictionary<string, string>();
   5:     filePath = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid() + ".txt");
   6:     using (StreamWriter writer = new StreamWriter(filePath))
   7:     {
   8:         writer.WriteLine(selectKeyword);
   9:         writer.WriteLine(insertKeyword);
  10:         writer.WriteLine(updateKeyword);
  11:         writer.WriteLine(deleteKeyword);
  12:     }
  13: }
  14:  
  15: [TearDown]
  16: public void TearDown()
  17: {
  18:     if (File.Exists(filePath))
  19:     {
  20:         File.Delete(filePath);
  21:     }
  22: }

Now I’m ready to create a method named Load that will read the data from the file and will store it into Keywords property. I’ll call it in LoadTest method before assertions. Code is below.

   1: [TestFixture]
   2: public class SqlKeywordsManagerTestFixture1
   3: {
   4:     private string selectKeyword = "SELECT";
   5:     private string insertKeyword = "INSERT";
   6:     private string updateKeyword = "UPDATE";
   7:     private string deleteKeyword = "DELETE";
   8:     private string filePath;
   9:  
  10:     public Dictionary<string, string> Keywords { get; set; }
  11:  
  12:     [SetUp]
  13:     public void SetUp()
  14:     {
  15:         Keywords = new Dictionary<string, string>();
  16:         filePath = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid() + ".txt");
  17:         using (StreamWriter writer = new StreamWriter(filePath))
  18:         {
  19:             writer.WriteLine(selectKeyword);
  20:             writer.WriteLine(insertKeyword);
  21:             writer.WriteLine(updateKeyword);
  22:             writer.WriteLine(deleteKeyword);
  23:         }
  24:     }
  25:  
  26:     [TearDown]
  27:     public void TearDown()
  28:     {
  29:         if (File.Exists(filePath))
  30:         {
  31:             File.Delete(filePath);
  32:         }
  33:     }
  34:  
  35:     [Test]
  36:     public void LoadTest()
  37:     {
  38:         Load();
  39:  
  40:         Assert.AreEqual(selectKeyword, Keywords[selectKeyword]);
  41:         Assert.AreEqual(insertKeyword, Keywords[insertKeyword]);
  42:         Assert.AreEqual(updateKeyword, Keywords[updateKeyword]);
  43:         Assert.AreEqual(deleteKeyword, Keywords[deleteKeyword]);
  44:     }
  45:  
  46:     private void Load()
  47:     {
  48:         using (StreamReader reader = new StreamReader(filePath))
  49:         {
  50:             string keyword = null;
  51:             while ((keyword = reader.ReadLine()) != null)
  52:             {
  53:                 Keywords.Add(keyword, keyword);
  54:             }
  55:         }
  56:     }
  57:  
  58: }

The final step is to create a real class let’s name it KeywordsLoader and to copy into it Keywords property and Load method but it should be extended to take a parameter that contains file to load path. Then the test should be re-factored – Keywords property and Load method are removed and real class instance is created into LoadTest method and the its Keywords property is tested. The full code of the final test is below.

   1: [TestFixture]
   2: public class SqlKeywordsManagerTestFixture1
   3: {
   4:     private string selectKeyword = "SELECT";
   5:     private string insertKeyword = "INSERT";
   6:     private string updateKeyword = "UPDATE";
   7:     private string deleteKeyword = "DELETE";
   8:     private string filePath;
   9:  
  10:     [SetUp]
  11:     public void SetUp()
  12:     {
  13:         filePath = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid() + ".txt");
  14:         using (StreamWriter writer = new StreamWriter(filePath))
  15:         {
  16:             writer.WriteLine(selectKeyword);
  17:             writer.WriteLine(insertKeyword);
  18:             writer.WriteLine(updateKeyword);
  19:             writer.WriteLine(deleteKeyword);
  20:         }
  21:     }
  22:  
  23:     [TearDown]
  24:     public void TearDown()
  25:     {
  26:         if (File.Exists(filePath))
  27:         {
  28:             File.Delete(filePath);
  29:         }
  30:     }
  31:  
  32:     [Test]
  33:     public void LoadTest()
  34:     {
  35:         KeywordsLoader loader = new KeywordsLoader();
  36:         loader.Load(filePath);
  37:  
  38:         Assert.AreEqual(selectKeyword, loader.Keywords[selectKeyword]);
  39:         Assert.AreEqual(insertKeyword, loader.Keywords[insertKeyword]);
  40:         Assert.AreEqual(updateKeyword, loader.Keywords[updateKeyword]);
  41:         Assert.AreEqual(deleteKeyword, loader.Keywords[deleteKeyword]);
  42:     }
  43: }

Conclusion. As you can see it is not difficult to create a unit test that will use a file on disk in order to perform its tasks. You also may not that the new implementation of the real class in this case is completely different than those in my previous post. Why? Because in the last time I’ve decided to create a test that will use an in-memory stream instead of file. That’s why I needed two classes one that will load data from file to a stream and one that will parse the data from the stream and will store it in some property. In current post I do need only one class that will do all operations because I choose to use a file on disk for the test. Which one do I prefer? I prefer the variant from my previous post (for sure, if I need a class that should perform all operations in one place I’ll choose the test method from current post). The downside of unit testing using a file on disk is that the unit test can become slower and at some point the developer will just ignore it and will not run it each time.

Leave a Reply