Dec 9 2008

Unit Testing in .NET Part 1 - Introduction to Testing

Category: Best Practices | Testing — Matt @ 04:43

Today is the first 'requested topic' post.  If you have a topic you would like to hear more about, drop me a line, and if it's something that I'm either interested in or working with anyway, I'll try to give the topic a proper post.  Serious suggestions only (yes, I'm talking to you, Evil Rob). 

Introduction

Today's post kicks off a series that's all about unit testing.  This series assumes no prior knowledge of unit testing, so it may start out a tad too basic for some, but it will carry all the way in to Test Driven Development. 

Let's start out with some background.  Software testing is simply the act of verifying the behavior of a piece of software.  The goal of testing is to uncover bugs (aka defects) so that they can be evaluated and possibly corrected.  Software testing is the bane of many coders' existence; that's one of the things that makes them coders instead of developers.  Testing is not like 'coding'.  You have to approach it with a different mindset.  Instead of trying to implement a feature, you are trying to break something.  Testing can be depressing.  No matter how much you test, you will probably never uncover all the bugs in anything more complicated than a 'Hello World' application.  Still, studies have consistently shown that testing improves the quality of software systems, reduces maintenance costs, and generally makes life better and more fun once you know how to do it correctly

There are numerous types of testing (and various ways to categorize all the different types).  An exhaustive list is beyond the scope of this post, but here are some of the more common types you may encounter:

  • Unit Testing - Tests that target a specific method or class, usually created by a single person or a small team.
  • Component Testing - Like unit testing, but taken up to the API or component level.
  • Integration Testing - Testing the component pieces together as an integrated system.  This type of testing can be difficult due to external dependencies, such as databases, web services, other network resources, etc.
  • Regression Testing - Re-running previous test cases to verify that no new defects have been introduced that break previously-working functionality.
  • System Testing - Testing the software system in its near-final configuration (beta testing could be considered a type of System Testing).
  • Load Testing - 'Loading' a system with work (such as traffic, method calls, etc.) to evaluate how it behaves under normal workloads. 
  • Stress Testing - Like load testing, except with workloads elevated beyond what are considered normal or expected levels.
  • Security Testing - Testing specifically for security exploits and vulnerabilities.

That's a long (but not exhaustive) list, and while each is probably worthy of a series of posts, I think the lowest-hanging fruit is unit testing.  It's easy to get started with (as we'll see today), enables regression testing, and has great tool support in the .NET world.  So, on to unit testing!

Unit Testing

The purpose of unit testing is to verify that a specific unit of code works as intended.  By 'unit of code', I mean a method or perhaps a class, but certainly nothing larger than that.  Typically, you will create a suite of tests for a library.  A suite is composed of multiple fixtures, which themselves are composed of multiple test cases.  Typically, a suite contains fixtures for an individual library, a fixture contains tests for an individual class, and a test case verifies one specific bit of functionality from a method.  A method should usually have multiple test cases.  You want each test case to verify a specific behavior of the method.  Take the following method as an example:

   1: public void DividePositiveNumberByTen(int number)
   2: {
   3:     if (number < 1)
   4:         throw new ArgumentOutOfRangeException();
   5:     else
   6:         return number / 10;
   7: }

Notice that the method actually does two things: if the parameter is less than 1, it throws an exception, otherwise it returns the result of dividing the number by 10.  You should create two test cases for this method, one that verifies that the method throws an exception when you pass 0 as an input, and the other that verifies that it returns the correct value for a valid input.  (We'll get in to the specifics of *how* you create those tests in the next post.)  In general, you want to test each distinct path of execution through your code.  This keeps your tests simple and clean, plus if a test fails, you will know exactly what is broken.

Unit Testing in .NET

The .NET world has almost a one-to-one ratio between developers and unit testing frameworks.  Some of the more popular ones are MbUnit, MS Test, and NUnit.  I started unit testing with NUnit many moons ago, and I've never found myself motivated enough to switch, so, I'm still using NUnit.  When coupled with TestDriven.NET, you can run your tests directly from within Visual Studio, which makes you more likely to actually run your tests than if you had to build your code, launch an external tool, load your compiled module, then find and execute the test.  Less resistance to testing is a great thing. 

Regardless of which framework you choose, unit testing in the .NET world couldn't be easier.  Most frameworks make defining a test fixture and test cases very easy: simply create a class marked with a special test fixture attribute, then add methods marked with a special test attribute to create test cases.  In addition to a way to mark test cases, every unit testing framework I've ever seen includes an API for asserting various things: that two objects are equal/not-equal, that an exception was thrown, that one value is greater-than/less-than another, and lots more.  Using these assert methods, you can create tests that verify the behavior of classes, method, etc.

That's it for the introduction.  Hopefully you have a basic idea of what unit testing is and how you might use it.  In the next post, we'll delve in to NUnit and its API as we begin writing unit tests!

Tags:

blog comments powered by Disqus