Page 1 of 1

C# Unit Testing Basics An introduction to unit testing, through the aide of visual studios

#1 SwiftStriker00  Icon User is offline

  • No idea why my code works
  • member icon

Reputation: 432
  • View blog
  • Posts: 1,596
  • Joined: 25-December 08

Posted 08 June 2009 - 09:42 AM

This is my first tutorial on this site and for C#, so please give constructive advice if I need to improve. Thanks.

This is an intro into Testing with MS Visual Studio, just simple basics to get you started, so you can see how it works, and give you a few ideas on how to do it. I will be using VS 2008, but it should work for 2005 as well.

So Lets start with a New Project. I chose a Console Application in the Windows Visual C# for simplicity. Name it and click ok. Now before we get started, make sure you have Debug and Test Tools, toolbars on. This will help us running our test code.

Ok now we need some stuff to test. So we are going to use a basic circle to explain things. So go to your solution explorer and on the project, right click > add class, and call it Circle.cs then copy this stub class below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace TestTut {
	class Circle {

		private Point myPoint;
		private double myRadius;

		/// <summary>
		/// Constructor for circle, default location, able to pass radius
		/// </summary>
		/// <param name="r">radius of the circle</param>
		public Circle(double r) { }

		/// <summary>
		/// Constructor for circle, able to pass in both 
		/// location and radius
		/// </summary>
		/// <param name="loc">Cordinate point circle lies on</param>
		/// <param name="r">radius of the circle</param>
		public Circle(Point loc, double r) { }

		/// <summary>
		/// Circle Property for radius. Get and Set radius of circle
		/// </summary>
		public double Radius {
			get { return 0; }
			set { double stub = value; }
		}

		/// <summary>
		/// Circle Property for diameter. Get and Set diameter of circle
		/// </summary>
		public double Diameter {
			get { return 0; }
			set { double stub = value; }
		}

		/// <summary>
		/// Calculate the circumference of a circle within 2 decimal places
		/// </summary>
		/// <returns>
		/// returns the double value of the circumference
		///</returns>
		public double getCircumference() {
			return 0.0;
		}

		/// <summary>
		/// Calculate the area of a circle
		/// </summary>
		/// <returns></returns>
		public double getArea() {
			return 0.0;
		}

		/// <summary>
		/// The pie-shaped piece of a circle 'cut out' by two radii.
		/// </summary>
		/// <param name="theta"> the angle between the radii must be 
		/// between 0(exclusive) and 360(exclusive)</param>
		/// <returns>returns the area of the sector</returns>
		public double getSectorArea(double theta) {
			return 0.0;
		}

		/// <summary>
		/// Calculates the length of a side of a 
		/// square that is circumscibed
		/// inside of the circle (Really make sure ur test code is working)
		/// </summary>
		/// <returns>Returns the length of one side of the square</returns>
		public double lengthOfCircumscribedSquare() {
			return 0.0;
		}

	}
}


So these stubs are going to be what were going to test and we are going to pretend that the developers are still currently developing them (all we know is how they are supposed to work)

Now once we have that, go to the menu Test > New Test. Select Unit test and name the file Test_Circle.cs and create a new Project (I called it Testing). See Fig.1 to make sure you have a similar Solution explorer to me.
Posted Image

Open up Test_Circle.cs, and letís take a look at what Microsoft did for us.
First note what namespace has been imported:
using Microsoft.VisualStudio.TestTools.UnitTesting;

This is critical to making test classes, methods, and cases. Next take a look at that [attribute] that has been given to the class:
Testing{
	[TestClass]
	public class Test_Circle {...}
}


This will tell Visual Studio how to make the class behave. Now delete all the code inside the class except TestMethod1 (leave its attribute!). The reason we are doing this, is because we arenít going to create an instance of this test so we donít need a constructor, and also we donít need the TestContext either. So letís clear clutter and get on with the good stuff.
----------
We are going to deal with 3 test method attributes in this tutorial
[TestInitialize]
[TestCleanup]
[TestMethod]
And the first 2 are real easy in concept. TestInitialize will be run before every TestMethod. We do this if we have a lot of setup to be able run a big test (i.e. you need to have a few managers running to get certain methods, or you want to have certain variable initialized before a test, or you want to have some thread running). TestCleanUp, runs after every TestMethod. We do this to shutdown service or de-allocate memory etc...
In our case we wonít really need it but we will put it in for good practice.

So before our TestMethod 1, letís put some variable declarations and our TestInitialize and TestCleanUp methods in. Since we are testing circles I figure we will want a Circle object, a point, and radius. Take note of the name of the circle I create, it will be important later. In the Initialize method, all I am doing is creating a random location for the point, and giving a default 1 value to radius, don't worry it will change. I'm leaving the Circle un-instantiated now, so we can do more testing with it. In the CleanUp method, I am just clearing all the variables for a fresh start for the next test method. (Make sure you add your other project's namespace and reference to you Test_Circle.cs so u can use Circle!)
Hereís what your class should look like now:
 Test_Circle.cs 


So letís rename TestMethod1 to Test_Properties. This is a really basic test just to make sure the accessors are working and the constructor is setting the variables (often trivial enough to omit, since accessors are pretty basic). Place the following code inside of Test_Properties method:
actual = new Circle(radius);

This is valid because actual was declared as class variable, and radius is not null because the TestInitialize will have run by this point. This leads us to the core way of comparing and testing; Asserts :cheer:

Asserts are apart of Microsoft.VisualStudio.TestTools.UnitTesting so we already to go with using them. The Assert class is filled with static methods used to compare data. The most common and the one we will primarily use is: AreEqual.
Now when dealing with Assertions you are compare what is expected to what is actually happening. Now when looking at the parameters for AreEqual we get (object expected, object actual, string failure_message).
So lets put that to use; add an assert method for the radius in test properties right below the new circle line, then make another one for Diameter (I will add an expected variable to help visualize the emphasis on expected and actual.
double expected = radius;
Assert.AreEqual(expected, actual.Radius, "Radius accessor is not returning radius properly");
expected *= 2;
Assert.AreEqual(expected, actual.Diameter, "Diameter accessor is not returning radius properly");


Congrats now you are set up to run your first test, and here you have some options. And we will see what those Asserts do too. Now if we want to run the whole TestMethod through completion we can run Test in Current Context. This is done by right clicking somewhere in the method and going to "Run Tests", or Test>Run>Test in Current Context, or ctrl+r,t. Or if you want we can step through with the debugger u can click on the tool bard that we setup "Debug Tests in current context" or ctrl+r, ctrl+t. if your debugging I suggest putting in break points (click on the line to left of the line number to toggle) so the debugger will stop part way through.

You should get this when you "Run Tests"
Posted Image

What happened here, was the code was compiled and ran, but when it got to the first assert through an error because they were not right. This is because "the developers" (aka we) didnít define our Radius and Diameter Properties. Note that the test will go until the first unhandled exception. So the order that you assert things are important (smallest to biggest is a good mindset).
letís quickly change our Circle properties from 0s to what the should be:
/// <summary>
/// Constructor for circle, default location, able to pass radius
/// </summary>
/// <param name="r">radius of the circle</param>
public Circle(double r) {
	myRadius = r;
	myPoint = new Point(0, 0);
}

/// <summary>
/// Constructor for circle, able to pass in both location and radius
/// </summary>
/// <param name="loc">Coordinate point circle lies on</param>
/// <param name="r">radius of the circle</param>
public Circle(Point loc, double r) {
	myRadius = r;
	myPoint = loc;
}

/// <summary>
/// Circle Property for radius. Get and Set radius of circle
/// </summary>
public double Radius {
	get { return myRadius; }
	set { myRadius = value; }
}

/// <summary>
/// Circle Property for diameter. Get and Set diameter of circle
/// </summary>
public double Diameter {
	get { return myRadius*2; }
	set { myRadius = value/2; }
}


Run your test again, and it should pass! Which means our constructor and accessors (Properties) are working. So lets try something a bit harder; getCircumference(). (C = 2*PI*r or C= PI*d)
Once again we will right the test case first pretending we canít see the Circle class. start by writing a new method (with the TestMethod Attributes)
and then creating an actual and expected objects. Finally assert their equivalence. Look something like this?
[TestMethod]
		public void Test_Circumfrence() {
			actual = new Circle(radius);
			double c = actual.getCircumference();
			//We round because the method description says it will rounded
			//to 2 decimal places, so we need to make sure we do the same
			double circum = Math.Round( Math.PI*2*radius,2 );
			Assert.AreEqual(circum, c, "Circumferences differ");
		}



And here is the dev code to use. For this I used a different formula, just to illustrate that we are not just comparing the same formula.
/// <summary>
/// Calculate the circumference of a circle within 2 decimal places
/// </summary>
/// <returns>returns the double value of the circumference</returns>
public double getCircumference() {
	return ( (( 16 / 5 - 4 / 239 ) - ( 1 / 3 * ( 16 / 53 - 4 / 2393 ) ) ) + .14)*2*myRadius;
}



That leaves 3 more methods to fill out and test, and I urge u to try and find a creative way for lengthOfCircumscribedSquare. But before I let you go off and try it all on your own. I will show you one more test technique
(This will be expanded upon in my next tutorial).

While simply Testing the whole object at the end Assert.AreEqual( circle1, circle2); will not give us very good information as to why it fails, so usually you want to try and assert almost every variable. But what happens if a method throws an error. For example our getSectorArea() will throw an ArgumentException if your input is not in the specified range. Well if thatís the proposed functionality, then we ought to test for that as well! But the problem is this will fail:
[TestMethod]
		public void Test_gSA_Bad() {
			double d = 9001;
			actual = new Circle(radius);
			actual.getSectorArea(d);
		}


And our beloved Asset class has methods to test exceptions. That is why we have the "ExpectedException" attribute (Yay)! What this means is we are going to actively look for a certain type of exception to be thrown, if it is the Test will pass. To make your method you pass ExpectedException an argument in your attribute line right after the TestMethod.
		 
[TestMethod, ExpectedException(typeof(ArgumentException))] 


You run it, and the test fails. So now our awesome developers finally get around to finalizing getSectorArea():
/// <summary>
/// The pie-shaped piece of a circle 'cut out' by two radii.
/// </summary>
/// <param name="theta"> the angle between the radii must be 
/// between 0(exclusive) and 360(exclusive)</param>
/// <returns>returns the area of the sector</returns>
public double getSectorArea(double theta) {
	if( theta <= 0 || theta > 360 )
		throw new ArgumentException("Angle must be between 0 and 360");
	return (Math.Pow( myRadius, 2) * theta)/2;
}


And now it should pass. But we should still right another method to test the mathematical correctness too. So make a Test_gSA().

So thatís it, those are your basics. Clearly when you get into test more complicated systems and equations you will truly see the usefulness of Testing. This will also mean the more creative you are writing your test cases, the more helpful you will be for developers finishing their code.

I have incorrectly finished Circle for you, try and make test cases based off the stubs before you download it, and see if you can figure out what is wrong! Good Luck

Attached File(s)



Is This A Good Question/Topic? 0
  • +

Replies To: C# Unit Testing Basics

#2 mperro  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 29-August 09

Posted 29 August 2009 - 11:06 AM

Hello,

Some pieces of code seem to be missing. I thought that maybe it was the browser that I am using, but changed the browser and still no code. After you explain that we want to add the TestInitialize and TestCleanup, all we can see as a piece of code is this:

Test_Circle.cs

:blink:

Can you help me?

Manon
Was This Post Helpful? 0
  • +
  • -

#3 Adkins  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 66
  • View blog
  • Posts: 560
  • Joined: 27-October 09

Posted 04 November 2009 - 06:30 AM

Could you please update this tutorial with the missing parts of code? Thanks! :^:
Was This Post Helpful? 0
  • +
  • -

#4 knichols  Icon User is offline

  • New D.I.C Head

Reputation: 10
  • View blog
  • Posts: 21
  • Joined: 09-November 09

Posted 09 November 2009 - 06:43 PM

Nice Article. Simple and to the point, thanks.

I think he meant something like this for the missing part.


	[TestClass]
	public class TestCircle
	{
		Circle ActualCircle;
		double ActualRadius;
		Point ActualPoint;


		public void TestInitialize()
		{
			ActualRadius = 1;
			Point point = new Point(3, 4);
		}

		[TestMethod]
		public void Test_Properties()
		{
			TestInitialize();
			ActualCircle = new Circle(ActualRadius);
			double ExpectedRadius = ActualRadius;
			Assert.AreEqual(ExpectedRadius, ActualCircle.Radius, "Radius accessor is not returning radius properly");
			Assert.AreEqual(ExpectedRadius * 2, ActualCircle.Diameter, "Diameter accessor is not returning radius properly");
			TestCleanUp();
		}

		[TestMethod]
		public void Test_Circumference()
		{
			ActualCircle = new Circle(ActualRadius);
			double c = ActualCircle.getCircumference();
			double circum = Math.Round(Math.PI * 2 * ActualRadius, 2);
			Assert.AreEqual(circum, c, "Circumferences Differ");
		}

		public void TestCleanUp()
		{
			ActualCircle = null;
			ActualRadius = 0;
			ActualPoint = new Point();
		}

	}



Was This Post Helpful? 3
  • +
  • -

#5 SwiftStriker00  Icon User is offline

  • No idea why my code works
  • member icon

Reputation: 432
  • View blog
  • Posts: 1,596
  • Joined: 25-December 08

Posted 22 November 2009 - 04:29 PM

		[TestInitialize]
		public void mysetupmethod() {
			ActualRadius = 1;
			Point point = new Point(3, 4);
		}


		[TestCleanUp]
		public void mycleanupmethod() {
			ActualCircle = null;
			ActualRadius = 0;
			ActualPoint = new Point();
		}



Sorry about not responding sooner it took one of my friends to read it and come and tell me since the response tracker was not on for the tutorial. knichols was close however you forgot to add the ever so important attribute tags to the init and cleanup methods. I renamed them above so there is no confusion about where TestInitialize and TestCleanUp should go.
Was This Post Helpful? 3
  • +
  • -

#6 rhyous  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 14-October 09

Posted 19 March 2012 - 02:26 PM

I myself am creating a Unit Test Tutorial of sorts, but it is a best practices as much as a guide.

You have helped me realized that I need an article on "How to create a Unit Test in Visual Studio 2010".

C# Unit Test Tutorial
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1