Testing is a part of every developers life. No matter how experienced a programmer you are, there are always times when, out of pressure or by mistake, something will go wrong. Sooner or later you will have to test your code to ensure that everything works according to plan. So, how can unit testing make our lives easier? Unit Testing is a tool that allows developers to easily test their source code and ensure that it works just fine, as if they had actually tested it. We are going to take a look at how it works, all the way from the beginning till you can create simple, yet effective, unit tests on your own.
Getting started
When testing, we compare the output of our project to what we are actually expecting to see. Supposing we create an application that sums up values. A simple test would be to try it out on two random numbers, 1 and 2. If the output is 3, our test was successful. However, the fact that we successfully got a 3, cannot ensure that by the time the projects is released, we will still get a 3.
Such issues and much more, which we will soon mention, are the reason we may choose to use unit testing. For the time being, let's create a simple unit testing example that will help newcomers to get to grips with the idea of unit testing.
Create the code to be tested
Unit tests can be applied to source code. We can use any project type we want, but let's try it out on a website. So, let's create a website and insert in its App_Code file the following file called Customer.cs.
What's the point of this class? Supposing a sales person wants to sort his customers depending on how "good" they are. To do so, he needs to know their monthly income, how much money they spent this month as well as their last month bill.
GetBalance will return how much monthly money the customer is still holding. A customer that spends all his money is a reckless one.
GetBillRatio will return the ratio of the customer's bill. The larger the ratio, the more generous the client.
IsVIPCustomer returns true if the customer has monthly balance greater than 1000 and ratio larger than 10%. We do not wish to disappoint such customers.
This is the class Customer.cs
using System;
public class Customer
{
string customerName;
double income;
double bill;
double lastBill;
//These strings will be used as messages when throwing exceptions later on
public string incomeMessage = "income less than bill";
public string lastBillMessage = "lastBill equals to zero";
public Customer(string _customerName, double _income, double _bill, double _lastBill)
{
customerName = _customerName;
income = _income;
bill = _bill;
lastBill = _lastBill;
}
public double GetBalance()
{
//We do not want to have customers with income less than bill
if (income < bill)
throw new ArgumentOutOfRangeException("income", income, incomeMessage);
return (income - bill);
}
public double GetBillRatio()
{
//bill ratio in comparison with last month's
if (lastBill != 0)
return (bill - lastBill) / lastBill;
else
throw new ArgumentOutOfRangeException("lastBill", lastBill, lastBillMessage);
}
public bool IsVIPCustomer()
{
//returns true when balance is more than 1000 and billRatio more than 10%
double balance = GetBalance();
double billRatio = GetBillRatio();
return ((balance > 1000) && (billRatio > 0.1));
}
}
A customer, whose income is less than the money he spends, is a lousy one. GetBalance will throw an exception if such condition happen, so we can treat this case properly.
GetBillRatio's value is created by dividing with last month's bill. A new customer's last month's bill will be zero and we definitely do not want to divide by zero. We chose to throw an exception instead.
This is the source code we want to test. We will test the way we described earlier, by creating proper input and checking if the output was what we expected it to be.
Creating the unit test
To have a unit test we will need to create a new project.
Using Visual Studio 2012 go to
File -> New Project -> Installed -> Templates -> (Choose your favorite language eg Visual C#) -> Test -> Unit Test Project
Using Visual Studio 2010 or previous versions go to
Test -> New Test... -> Unit Test
Regardless of what edition you use, this will create our new Unit Test Project accompanied by its first file UnitTest.cs
A unit test file at minimum consists of the following:
-
A reference to the Microsoft.VisualStudio.TestTools.UnitTesting library
-
A public class declared as TestClass eg
[TestClass]
public class UnitTest
-
A void method declared as TestMethods eg
[TestMethod]
public void TestMethod()
Each TestMethod we create, provides us with a new test. In other words, to create a new test we will create a new TestMethod.
Test Methods
Earlier, we mentioned that, based on our specifications, GetBalance method should throw an exception in case income is less than the bill.
Let's create a new TestMethod that will ensure this will always happen.
[TestMethod]
public void TestMethod()
{
Customer cust = new Customer("Chris", 200, 500, 200);
double balance = cust.GetBalance();
}
We have just created a Customer object whose income is 200 and bill is 500. This should throw an exception if GetBalance is called.
Now we need to run our test and see if this actually happens. Go to Test -> Run -> All Tests. This will run all tests and provide as with our valuable info.
In the picture there are four test methods. We can see which ones passed the tests and which didn't. Our method TestMethod seems to have failed the test.
This is what we've been expected. The code was correct and the exception was thrown. Yet, there is something odd about it. Even though our test got successful results visual studio sorts it out as a failed test. Yet, we would actually want to think of this test as a successful one. So, what we should do is inform the method of the exception we are expecting to get, like this:
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void TestMethodForBalance()
{
cust = new Customer("Chris", 200, 500, 200);
double balance = cust.GetBalance();
}
Running our test once again, we are informed that it passed!
Now, we would like to create a test that will ensure that a customer with 2000 income, 500 bill and 200 last month's bill will throw no exception. So, we write
[TestMethod]
public void TestMethod()
{
Customer cust = new Customer("Chris", 2000, 500, 200);
double balance = cust.GetBalance();
double ratio = cust.GetBillRatio();
bool isVIP = cust.IsVIPCustomer();
}
That will be a successful test as well.
Why unit testing?
You are probably starting to realize why unit testing is important. We have just created a test which ensures that this kind of customer will throw no exceptions. Now, the source code we are testing is likely to be altered in times. However the test will still be there. And then, no matter who is going to be assigned this, the developer may run this unit test and ensure that what he has changed does not interfere with the initial specifications.
This is only one test method. Supposing the first developer had created test methods enough to check all possible results. In that case, it would only take a few seconds to the new developer to run the tests and ensure that everything still works fine.
Apart from this, unit testings can provide a developer with easy understanding of what the source code is supposed to do and thus keep a major summary of the specifications, without needing tons of paper by his/her side.
A second look on Test Methods
So far we have created test methods that can only help us with exception issues. Yet, this is not enough. For example, we may wish to know if our friend Chris is a VIP customer. To answer that we need the IsVIPCustomer method we created earlier. However, right now we are testing. We have done our homework and found out that Chris is a VIP customer. How can we test this?
To accomplish that, we are going to use the Assert class that unit testing offers to us. Assert contains many methods such as AreEqual, IsNull and IsTrue. What Assert's methods do, is compare the input with an expected value. For example in the example where we want to test if IsVIPCustomer is true we will use
[TestMethod]
public void TestMethodWithAssert()
{
Customer cust = new Customer("Chris", 2000, 500, 10);
bool isVIP = cust.IsVIPCustomer();
Assert.IsTrue(isVIP);
// We may also use this: Assert.AreEqual(isVIP, true);
}
This TestMethod is successful and, this way, we ensure that we get the result we expect. That is to say, Chris is a VIP. However, if Chris was not a VIP (IsVIPCustomer returns false) then our test would have failed.
This is the last test method we will examine. A while ago we tested that GetBalance throws an exception if income is less than the bill. However, if we wanted to test both GetBalance and GetBillRatio methods but wanted to check only for GetBalance exception, this would be no good as they both throw the same type of exception. Take a look at the class we are testing. These two methods throw exceptions, however each one has a different message carried within it. So, in order to separate them, we will have to test that message. To do so, this time we will use StringAssert class. This is a class similar to Assert that is mostly fit to string testing. It contains methods such as Contains and EndsWith. This is our code.
[TestMethod]
public void TestMethodWithExceptionAssert()
{
Customer cust = new Customer("Chris", 200, 500, 100);
try
{
double balance = cust.GetBalance();
double ratio = cust.GetBillRatio();
bool isVIP = cust.IsVIPCustomer();
}
catch (ArgumentOutOfRangeException e)
{
StringAssert.Contains(e.Message, cust.incomeMessage);
}
}
This test will pass as well. In that case we can tell that either no exception was thrown or an exception was thrown by GetBalance.
TestInitialize and TestCleanup
So far we have learned how to create simple test methods. Yet, unit tests offer two more, easy to handle, attributes. TestInitialize and TestCleanup
Using TestInitialize on a method we declare that this method will be run before all test methods do.
Using TestCleanup on a method we declare that this method will be run after all test methods do.
These methods can be used to create and remove resources that will be used by the test methods. For example we can use TestInitialize in the following way:
[TestClass]
public class UnitTest
{
Customer cust;
// Use TestInitialize to run code before running each test
[TestInitialize()]
public void MyTestInitialize()
{
cust = new Customer("Chris", 2000, 500, 200);
}
[TestMethod]
public void TestMethod()
{
double balance = cust.GetBalance();
double ratio = cust.GetBillRatio();
bool isVIP = cust.IsVIPCustomer();
}
}
Should I use Unit Testing?
There is no straight answer to this question. You may choose to use it or not depending on what you feel like. Unit testing, as told already, can be used to create tests that will wait there to be run whenever you want. It can also be used as specs guide.
However creating unit tests requires amount of time. Do you always feel you have the time necessary to use unit testing? A script schedule does not allow a developer such comforts. However, a well-organized schedule may offer some time to spend on unit testing and thus helping you keep everything in one place.
In general, you should keep in mind that unit testing will give you long-term results and not something that can be seen in a few days time.
Conclusion
Unit testing is a developers testing test. Unit Testing requires a new project consisting of a TestClass class and one or more TestMethod methods. Inside these methods we can test our code for exception handling, parameter and method returning values and much more as if we were testing the code through a user interface. Even though unit testing can be very useful it can also be time consuming so you should take such things into account before starting to use it in your projects.