NUnit and Test Driven Development

Adam Main

Software Engineering

University of Wisconsin Platteville

Abstract

There are many different ways to develop software. Waterfall was one of the first, but certainly not the last. Which is the best, or is there even a best? Test driven development is a fairly new addition to the countless processes around for software engineering, but is it worth using or even possible? The people who developed NUnit certainly think so, so lets take a closer look.

It offers continual testing of the software, which prevents the actual testing phase from being considerably shortened by deadlines. It keeps the management informed of progress, and it gives the developer instant feedback on the software he or she is writing. However, that is just the beginning.

Test Driven Development

The Basics

Test driven development has a very simple process.

  1. Write a test that fails.
  2. Write code to pass the test.
  3. Refactor the code.
  4. While the program isn’t finished, goto step 1.

It can be summed up with two rules: Write new code only when a test fails, and eliminate duplication. You should never write more code than is needed to pass the test, and you should only try to pass one test at a time. Also, failing to compile is a failure to pass a test.

Write only a simple test or two at a time, then the corresponding code to pass them. Make the tests concise, simple, and complete. Once you finish a test, clean up the code and refactor it to make it more readable and simpler. Remove any duplicated code to comply with the rules.

Most importantly, test driven development is not about testing, but about design and knowing what the system does.

The Advantages

This approach keeps programs concise and simple, without unused and untested code. It provides testable and tested code. It allows the programmer to know the progress of the program.

Test driven development removes the need for manual testing. Manual testing is time consuming and error prone. Every time you change your code, you need to go back and retest. This can take a long time depending on the change. Any human errors could cause the whole test to be scrapped. Manual testing also limits your ability to test specific functions or units, and it not always reproducible by another person.

TDD also forces you to know the specifications and requirements of your program. It is very hard to write a test for something you do not understand. This will reduce the numbers of requirements related errors, and cause the developer to remove any ambiguities in the requirements prior to coding, instead of just assuming something that is not necessarily true.

Test driven development causes a high percentage of code coverage, since every piece of code is written only to a pass a test. The tests check a useful feature or requirement the system will eventually perform. Thus, each line of code should always be run under a good system test.

Code written is highly testable, readable, and concise. The basic concept of TDD causes all code to be testable. It serves a single purpose, because it is required to.

The code is very cohesive and highly decoupled. “TDD proponents argue that reduce coupling occurs because the practice guides towards the building of objects that are actually needed (to pass test cases based on the requirements) rather than building objects that are thought to be needed (due to possible improper understanding of requirements)”. [2]

The Disadvantages

One of the biggest disadvantages when doing test driven development by the book is the shear number of compiles needed to create software. Even the simplest functions that have a few different exceptions may need to be coded, compiled, and refactored ten, twenty, or even fifty times. Since you only write just enough code to pass the tests you write, you don’t look at the overall algorithm and code it right the first time through. If you did that, you would be writing code that isn’t run and isn’t explicitly needed in the test, even though it is the code you will eventually need.

Testing is good, but it should not be everything. Most proponents of TDD insist that very little should be done in the way of actual design. They insist 15-20 minutes with a group of people and whiteboard is all you need.

The lack of planning makes it very hard to get a good start on large systems with many different programmers working on separate pieces. It also makes it difficult to organize individual tasks for programmers, as the parts themselves are not nailed down.

This also makes it very hard to select hardware if you are doing systems engineering. Without a good understanding of the software architecture, you could make a large error in selecting the hardware architecture.

NUnit: Unit testing for .NET systems

What is NUnit?

NUnit is a basic system for unit testing components designed in .NET. It uses special metadata to setup, test, and cleanup components that conform to the NUnit standard. Its uses .NET’s attributes to define tests and test classes. It is written in C#, and based on the xUnit standard.

It has two separate applications to run and manage tests (a GUI and a console). It can load most files that visual studio creates to run and test them.

Why use NUnit?

NUnit provides a simple interface to do all the unit testing for a project. It also provides applications that give constant feedback about the test results. The GUI allows you to easily configure which tests, subsets, and packages of tests need to be run. It allows you to save the test projects for later use as well.

It provides XML output for use with other applications to easily understand and analyze the results of the tests.

How do you use NUnit?

To use NUnit, download the framework and tester applications from NUnit.org. Create your test file(s) in the .NET language of your choice and add a reference to the framework library.

Use the .NET attribute [TestFixture] before each class declaration that has any test functions in it. The class must have a publicly accessible default constructor in order to work. Having no constructors works as well, since the compiler creates an empty public default constructor.

Put the attribute [Test] before any function that you want to be a test. Each of these methods should be public and void with no parameters. The functions should use the static methods in Assert class of the framework for the actual tests. The Assert functions are used during the test to provide feedback to the testing application.

Assert Functions

Each assert function has three overloads, with the exception of AreEqual() which has many more. There is the basic function without any extra parameters, one with just an extra string parameter, and one with both an extra string parameter and an array parameter. IE:

Assert.IsFalse( bool condition);

Assert.IsFalse( bool condition, string message );

Assert.IsFalse( bool condition, string message, object[] parms );

The message parameter is used to give the testing application more information about a test, when it fails. The parms array is used to give more information about the test that caused the Assert function to fail.

Most of the functions are pretty self explanatory. For instance, IsFalse( bool condition) fails if the condition is true. The rest of the functions are as follows:

  • IsTrue( bool condition)
  • IsFalse( bool condition)
  • IsNull( object obj)
  • IsNotNull( object obj)
  • Fail()
  • Ignore()
  • AreEqual(int actual, int expected)
  • AreEqual(double actual, double expected)
  • AreEqual(float actual, float expected)
  • AreEqual(decimal actual, decimal expected)
  • AreEqual(object actual, object expected)
  • AreSame(object actual, object expected)

The Fail method always fails, and Ignore will cause a test to be ignored. AreEqual(object, object) uses the object’s .equals() method, while AreSame(object, object) tests for pointer equality.

You should try to limit each test function to one Assert function, since any Assert test after an Assert failure will be ignored in the rest of the function. This helps to make your tests concise and simple as well, which will give you better feedback.

Attribute Tags

The [TestFixture] and [Test] attributes are the basic attributes used by NUnit, but there are a few more that make testing much easier.

[TestFixtureSetUp] and [TestFixtureTearDown] are test constructors and destructors. They are run before and after each test function is called. They allow you to have remove redundant setup code from each of the tests. However, only one of each is allowed in a class. If there is more than one, the tests will run, but the setups and teardowns will not.

The [ExpectedException(typeof(ExceptionType))] attribute is placed between the [Test] attribute and the function header. This causes the test to fail if the expected type of exception is NOT thrown by the test function. It eliminates the need for large try-catch blocks that use Assert.Fail().

To easily manage tests the [Category("Category Name")] attribute can be used. This attribute also is placed between the [Test] attribute and the function header. It allows the programmer to select a series of functions to run from the tester application. You can access categories in the GUI using the category tab or in the console application by using the /exclude or /include arguments.

The [Explicit] tag causes a test to be skipped by default unless selected in the GUI or included in the console with /include. It is useful for removing tests that are very time consuming or inefficient. It will also be run if the category the test is in is specifically included or run as well.

The [Ignore(“reason”)] tag is useful for commenting out a test that is unused or incorrect. It is better than using actual comments or actually removing the test because it still appears in the test output. This will keep the test from being accidentally left out in the future, while still keeping it from being executed.

Projects

You can save test projects using the GUI tester application. When you load a library or executable file that contains testing attributes, the GUI application will look for the metadata and automatically add each of the tests to a project. You can add multiple files, and select individual tests or categories of tests. When you save your project, your selections will be saved as well. The GUI can load visual studio project files and solutions, and parse through them to find the tests. It will use the namespaces, classes, and categories to group the tests. Projects are saved with a .nunit extension.

Conclusion

Finding a middle ground seems to be the best place for Test Driven Development. It won’t work great for all situations, and in some it could be near disastrous. However, unit testing and small scale TDD is useful in nearly every project imaginable. It can be used with existing OOD processes, and would greatly improve the quality of those projects.

NUnit is a great tool that is simple and easy to use. It takes very little time to learn, but pays off big dividends in terms of quality, especially coupling and cohesion. It takes a little more work to separate a program into easily testable units, but it is well worth the effort.

References

[1] NUnit - Documentation. Retrieved March 28, 2005, from

[2] George, Boby and Laurie Williams. An Initial Investigation of Test Driven Development in Industry.Retrieved March 30, 2005, from

[3] Ganssle, Jack. XP Deconstructed. Dec 10, 2003.

[4] Provost, Peter. Test Driven Development in .NET.

[5] Introduction to Test Drive Development (TDD). Retreived April 1, 2005, from

[6] Mugridge, Rick. Test Driven Development and the Scientific Method.