Introduction
Why test your application? After all, most developers are pretty smart people, right? There are a lot of smart people, but it is easy to make mistakes, or to have incorrect thinking in regards to how an object works. In addition, as we make enhancements or changes to the system, tests ensure that we don't break the code already written. Fortunately, we have a tool that can help ensure we don't make mistakes when coding.
NUnit relies on a set of conditions that you program in. For instance, when using a routine that makes a mathematical calculation, if you know all of the parameters, you should know what the result is, and you can ensure that it works. This goes the same with most of our code; when having a specific set of inputs, we should have a desired output. In some cases we don't know; however, in those cases, we just want to verify that we get a response.
NUnit Attributes
NUnit comes with a set of attributes that we will see in action later. The first I will talk about is the TestFixture; this attribute is at the class level, and denotes that the class is a test class that the NUnit testing software should recognize. Within each class, there are methods that are the actual test, designated by the Test attribute. You can also have methods run when the test class set's up, called TestFixtureSetup, and for when shutting down, called TestFixtureTearDown. These methods let you setup the objects you will work with, and close them properly. These are the attributes you regularly work with for conducting tests.
NUnit Testing Example
Suppose we want to test a Customer class. The customer class has a rule that it must have the first and last name provided, and can only exist if it has at least one order. We can conduct this test:
[TestFixture]
public void CustomerTest
{
[Test]
public void TestRetrieveCustomer()
{
Customer customer = CustomerDAL.GetCustomer(1);
Assert.IsNotNull(customer);
Assert.IsNotNull(customer.FirstName);
Assert.IsTrue(customer.FirstName.Length > 1);
Assert.IsNotNull(customer.LastName);
Assert.IsTrue(customer.LastName.Length > 1);
Assert.Greater(customer.Orders.Count, 0);
}
}
The customer class we have is returned from the data access layer, in this example. We get it by ID, and then perform a series of tests through the Assert class. This is a static class with a wide array of tests, such as testing whether an expression is true, false, is null, is not null, is of a specific type, and many other assertion types.
Specifically, the first assert statement makes sure we have a valid object with this valid ID. The next statement verifies the first name, and that the length of the name is greater than one. This is also checked for the last name, followed by the Greater method that ensures the count of the orders collection is greater than zero.
What to Test
That previous example shows an actual test, but it doesn't really look like a good test. For instance, if we are pulling information from the database, shouldn't we know that first/last name is required? However, I would recommend testing that the values are still being pulled from the database correctly anyways, because there could be a bad assignment in the code. To take it further, it should also test multiple customers pulled back from our data layer. Preferably, testing collection data at the first, middle, and end ranges is a good idea.
Also, if you have any complex logic in the data layer or in the customer class, like a calculation of total order cost, it should also test any complex logic as well, to ensure that the calculation of all the orders adds up correctly. As another example, if we have a routine that calculates a bonus based on the total amount of purchases a customer made, which the number of months as a customer is passed in as a parameter, we should be able to calculate the final result. Take a look at this example:
[Test]
public void TestBonusCalculation()
{
BonusCalculator calc = new BonusCalculator();
//Use a known original salary so that we can predict the result
calc.OriginalPurchaseAmount = 1000;
Assert.AreEqual(100, cal.CalculateBonusByMonths(5));
Assert.AreEqual(120, cal.CalculateBonusByMonths(6));
Assert.AreEqual(140, cal.CalculateBonusByMonths(7));
Assert.AreEqual(180, cal.CalculateBonusByMonths(8));
Assert.AreEqual(220, cal.CalculateBonusByMonths(9));
Assert.AreEqual(300, cal.CalculateBonusByMonths(10));
}
Tests are great for testing complex logic, like string manipulations and regular expression parsing. This is because it is so easy to chop off a single character, and testing ensures that the results returned are correct down to the character.
[Test]
public void TestStringManipulation()
{
string regex = "\d+(,\s*\d+)?";
Assert.IsTrue(Regex.IsMatch(regex, "9,2"));
Assert.IsTrue(Regex.IsMatch(regex, "9"));
Assert.IsTrue(Regex.IsMatch(regex, "9, 2"));
Assert.IsTrue(Regex.IsMatch(regex, "12345, 67890"));
Assert.IsFalse(Regex.IsMatch(regex, "9/2"));
Assert.IsFalse(Regex.IsMatch(regex, "9.2"));
}
The test below calls a method that returns some data from a database, which we validate that the expected results are returned. Testing against a method that returns data is tough to do, because you can never guarantee the results if you don't find a way to isolate the test before the test is run (like recreate the database structure, as a friend of mine did, delete all the data, or restore it to an original state). So the database presents its own challenges; at a minimum, you can perform some basic tests:
[Test]
public void TestCallDataMethod()
{
DataTable results = DataObject.GetData();
Assert.IsNotNull(results);
Assert.Greater(results.Rows.Count, 0);
// Additional testing could be looping through
// the rows to ensure data is correct and in the right format
// You can also test any other data operations
// you may use, such as working with data views
DataView view = results.DefaultView;
view.RowFilter = "Field = 'Test'";
Assert.Greater(view.Count, 0);
}
What Else to Test
What else can you test? Many things. For instance, if you have a custom collection, you can test retrieving items from the collection. You can test that certain methods throw a specific kind of exception when certain criteria are met (such as null parameters throwing ArgumentNullException). This is done using the ExpectedException attribute. You can test your business and data layers. It can be hard to test the actual interface, because oftentimes the methods in web pages or windows forms aren't exposed, and a lot of web page/windows rendering logic should be the actual source of the test. However, if you move more of your code to an MVC-based approach, the accuracy and range of your tests will increase. There are additional testing tools available for testing the windows/web environments; however, more support exists for 1.1 applications than 2.0.
Summary
This article shows you how to construct an NUnit test, and looked at some of the logic you need to conduct tests.
References
If you want to see a sample application and the tests created for it, you can go to my Reminder.NET application on Code Plex. You can also find out more about NUnit on their site.
About Brian Mains
 |
Brian Mains is an application developer consultant with Computer Aid Inc. He formerly worked with the Department of Public Welfare.
In both places of business, he developed both windows and web applications, small and large, using the latest .NET technologies. In addition, he had spent many hou...
This author has published 49 articles on DotNetSlackers. View other articles or the complete profile here.
|
You might also be interested in the following related blog posts
VS 2010 and .NET 4.0 Beta 2
read more
IDCC conference in Israel – Spet. 14th
read more
Testing.StackExchange – The “StackOverflow” for Software Testers
read more
Telerik Launches RadControls for Silverlight 3 for Line-of-Business Application Development
read more
Telerik Announces Support for Microsoft Silverlight 3
read more
Teleriks Q2 2009 Release Expands All-in-one .NET Offering
read more
Telerik Introduces Free Web Testing Framework for ASP.NET AJAX and Silverlight
read more
Continuous Integration with WebAii for Silverlight, NUnit and CruiseControl.NET
read more
July's Toolbox Column Now Online
read more
Load Testing vs. Profiling
read more
|
|
Please login to rate or to leave a comment.