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 :)