Tuesday, October 13, 2015

Unit Testing



In this post lets try to answer a few questions on unit test. Would be great to hear from you which would help to improve my knowledge J

1.    What is NOT a unit test?
A Test which:
·           Talks to the database.
·           Communicates across the network
·           Touches the file system
·           Can’t run at the same time as any other unit tests
·           have to do special things to your environment to run it

2.    What is the necessity of a unit test and how does it lead to good maintainable source code?

            Existing unit tests benefits developers in at least two ways:
            1. Unit   tests provide concrete expressions of business requirements. It also provides the developer with debugging opportunity for deeper understanding of the existing code,.
            2. Ensures that changes made by the enhancement do not break the existing system, or gives the developer a testing pathway to test the existing behavior based on the enhancements.

  Adding new tests provides the following benefits:
  1. They ensure that code meets requirements.
  2. They ensure that all logic conditions are accounted for. They force the developer to   revisit the code they have just written and to approach it from different directions. This helps to find out conditions the developer might otherwise not think of, resulting in code changes to properly handle these conditions.

3.         How to start writing unit tests?
Following have to be kept in mind before we venture to write unit tests:
·         Understand what the code is doing :)
·         What is our assertion or expectation in test case? One and only One assertion per test.
·         How to mock the dependencies?
·         Name the test evidently; it should explain the business requirement that was achieved with this unit module.

Consider the following class:
internal class MostRecentlyUsedList : Collection
   {
       private readonly int m_maxSize;

       internal MostRecentlyUsedList(int maxsize)
       {
           if (maxsize <= 0)
           {
               throw new InvalidOperationException("Recent lists limitation should be more than 0.");
           }
           m_maxSize = maxsize;
       }

       ///
       /// If size exceeds more than maximum count,
       /// remove the first element and add the new element at last.
       ///
       /// item to be add
       internal void Insert(T item)
       {
           if (object.Equals(item, null))
           {
               throw new ArgumentNullException("item");
           }

           int itemPosition = Items.IndexOf(item);
           if (itemPosition == -1) //value not found
           {
               if (this.Count == m_maxSize)
               {
                   RemoveLastElement();
               }

               InsertAtFirst(item);
           }
           else
           {
               BringItemToTop(itemPosition);  
           }
       }

       ///
       /// If collection size is more than max size, collection will be trim to max size
       ///
       /// Existing Collection
       internal void AddRange(IEnumerable collection)
       {
           if (object.Equals(collection, null))
           {
               throw new ArgumentNullException("collection");
           }

           IEnumerable tempEnumerable = collection as T[] ?? collection.ToArray();
           if (tempEnumerable.Count() > m_maxSize)
           {
               tempEnumerable = tempEnumerable.Take(m_maxSize);
           }
           tempEnumerable.ForEach(obj => Insert(Count, obj));
       }

       internal void Replace(T original, T newone)
       {
           int existingpos = this.IndexOf(original);

           if (existingpos == -1)
           {
               throw new ArgumentException(string.Format("{0} not exist to edit.", original));
           }

           this.RemoveAt(existingpos);
           this.Insert(existingpos, newone);
       }

       #region Private methods
   
       private void InsertAtFirst(T item)
       {
           Insert(0, item);
       }

       private void RemoveLastElement()
       {
           this.RemoveAt(Count - 1);
       }

       private void BringItemToTop(int itemPosition)
       {
           T selectedItem = Items[itemPosition];
           Items.RemoveAt(itemPosition);
           InsertAtFirst(selectedItem);
       }

       #endregion
   }

Now we can start writing test for this class:

Suddenly, a question will arise, for which method (module) should a unit test be written?

All the public methods are testable, which interns call the private or protected methods. So if we write tests for public methods, it will cover the private or protected methods of the class.

Consider method => Insert()

List out test cases for Insert() method:

·     It should throw exception when null value is passed as argument
·     Insert the items more than MAXSIZE, list size should not be more than MAXSIZE
·     Insert the fresh item=> should insert at zeroth position
·     Insert existing item=> existing item should bubbled up in list

Now write all test cases separately:

Normal criteria to write test case name => MethodName_Scenario_Expectation
Test body is divided into three parts:
Arrange: setup everything needed for the running the tested code. This includes any initialization of dependencies, mocks and data needed for the test to run.
Act: Invoke the code under test.
Assert: Specify the pass criteria for the test, which fails it if not met.
  
  •  It should throw exception when null value is passed as argument
[Test]
       public void Insert_InsertNullItem_ThrowsException()
       {
            //Arrange
           const int maxSize = 5;// Don’t use magic numbers           
MostRecentlyUsedList mrulist = new MostRecentlyUsedList(maxSize);
           //Act, Assert
           Assert.Throws(() => mrulist.Insert(null));
}

  • ·         Insert the items more than MAXSIZE, list size should not be more than MAXSIZE

[Test]
public void InsertItems_MoreThanRecentListMaxCount_ListSizeShouldBeEqualToMaxCount()
       {
           //Arrange
           const int recentlyusedlimit = 4;
    MostRecentlyUsedList mrulist = new MostRecentlyUsedList(recentlyusedlimit);

           //Act
           mrulist.Insert(1);
           mrulist.Insert(2);
           mrulist.Insert(3);
           mrulist.Insert(4);
           mrulist.Insert(5);
           mrulist.Insert(6);

           //Assert
           Assert.AreEqual(recentlyusedlimit, mrulist.Count);
       }

Finally, most important is modify MostRecentlyUsedList=> Insert() method implementations , run tests again . If it fails, then our tests are good :)

In the same way we can write tests for remaining methods.

In the next blog I would write about,
  • Bad class design/implementation which are not testable. How we can convert to testable class.
  • Mocking techniques in C# to mock dependency  
Hope you enjoyed the read!, awaiting for your suggestions

Thanks a lot :)