Click here to Skip to main content
15,884,634 members
Articles / DevOps / Unit Testing
Tip/Trick

General C# MS Unit Test Tips for a New Development Project

Rate me:
Please Sign up or sign in to vote.
4.38/5 (5 votes)
20 Oct 2015CPOL7 min read 19.2K   14   2
When starting a new C# project, put in a test project to unit test your code as you develop. This is test-driven development.

Introduction

When I'm starting a new project, I often forget how to start it in a test driven way. Here are some general reminders useful for C# development. Developing with tests is much better than trying to add them retrospectively. You will have less bugs, and more confidence in your code.

What is Test-driven?

Test-driven is an approach to developing software. There is no excuse anymore for not writing tests as you develop, there are lots of test frameworks free of charge to use (for example MS Test built into Visual Studio). Best practices dictate every time you develop some code, follow these steps:

  1. Resolve your requirement. Remove any ambiguity, stipulate concisely what you are trying to achieve. Consider error conditions and edge cases, the "what-ifs" ?
  2. Develop a test. Write a test that will prove the function you are writing works exactly as you want it to. Then, add some tests to make sure if it fails you handle the failure in a graceful manner, nobody wants to use software that bombs out periodically.
  3. Develop your function. Use comments for any non-obvious code. Use sensible variable and function names.
  4. Add logging. I use different levels of logging. For example, I add a log entry for every function as an Info i.e. "FunctionA called with these params {0}", and caught exceptions will print an Error. Make sure your error messages give enough information if possible, for the user to sort the problem out themselves, thus cutting down on lots of support calls, and bug reports, etc.

Of course, if you have an automated build process, then after you check in your code to a repository, make this process execute the tests as well (if you can auto build and test all your configurations i.e. Release/Debug then that's event better). This will make sure no matter how finished the project is, the code is at least still runnable !

Test Project

Always start your development with a test project, to set one up simply create one, File->New Project->Templates->Visual C#->Test->Unit Test Project.

Link the code you want to test into your test projects references.

Creating Test Classes and Tests

All test classes that contain tests should be public. Here are some reminders of the MS Test annotations...

  • [TestClass()] - Makes the class a test class
  • [TestMethod] - Marks the function as a test function
  • [Description("Test the set up of command parameters")] - Add a description about the intention of the test
  • [Ignore] - Don't run the test as default. Ignore the test if you want to keep the test code, but only want to test it occasionally. This is especially useful if it leaves a device in a undesired state, i.e., reboot on an ios device, or if the test takes ages to run.
  • [ExpectedException] - Use this annotation if you expect your test to throw an exception. Note if your test throws more than one exception in different places, then see the tip below for using try and catch with Fail instead.

Test Conditions

When testing, use the Assert class. Remember you are making sure something is true. For example, Assert(This is true).

Note that you write an exception with what you expect as the first parameter and what you actually got as the second. The arguments are ordered, if you swap them around, you will get false information, i.e., Expected null, got a valid reference is not the same as Expected a valid reference and got a null!

Here are some Assert functions:

  • IsTrue - Is the statement true
  • IsFalse - Is the statement not true
  • AreEqual - Are the two equal
  • IsNull - Is the reference null

Fail

When testing, try not to be only optimistic, have tests that pass in nulls, files that don't exist, etc. If we hit a Fail assert, then chances are the code you are testing hasn't thrown an exception or caused what you expected. If the test should throw an expected exception use the [ExpectedException] annotation, your test will pass only if the exception is thrown. If however you expected several exceptions consider this approach i.e.:

C#
[TestMethod]
[Description("Test the pairing of the device")]
public void DeviceTargetConnect(string deviceId)
{
            IOSDevice targetDevice = new IOSDevice(Id);

            //Dont support callbacks on disconnect
            Assert.IsFalse(targetDevice.SupportCB);

            //Do support connecting
            Assert.IsTrue(targetDevice.SupportConnection);

            //Connect to device
            try
            {
                targetDevice.Connect();

                //Now make sure we are connected
                Assert.IsTrue(targetDevice.IsConnected());
            }
            catch (Exception exc)
            {
                Assert.Fail("Connecting to the device failed. 
                	Exception : " + exc.ToString());
            }
}

Setting Up and Tearing Down

You can have functions that are called at certain times to set things up, and then to tear them down again.

Note that these signatures are sacrosanct, deviate and your tests will compile fine but then either bomb out before any code is called, or fail silently by the function not being called at all.

Check the Test dialog in visual studio and you will see the exception being raised if your signature isnt perfect, you will not be able to debug anything to discover what has gone wrong if for example your assembly functions signatures are not as expected.

Here are the annotations:

[AssemblyInitialize] - Gets called once before all the tests are run (before ClassInitialize) Do not use with ASP.Net:

C#
public static void AssemblyInit(TestContext context)

[AssemblyCleanup] - Gets called when test suite has finished (after ClassCleanup):

C#
public static void AssemblyCleanup()

[ClassInitialize] - Gets called when the test suite begins (when the test run begins):

C#
public static void IOSTestSettings(TestContext context)

[ClassCleanup] - Gets called when test suite has finished (when the test class goes out of scope):

C#
public static void CleanIOSTestSettings()

[TestInitialize] - Gets called before each individual test function:

C#
public void StartTest()

[TestCleanup] - Gets called after each individual test function:

C#
public void EndTest()

Note that  if you do not declare them as public, they will not get called, and sometimes people put function brackets after the annotation, i.e., [TestCleanup()], but this makes no difference.

Print Output TTY

When you want to output any text in Visual Studio, use:

C#
System.Diagnostics.Debug.WriteLine("Hello Dave ");

This will print out your text in the Immediate Window, not the Output window.

Temporary Dialogs

Sometimes, you want to run a test and make sure other threads are behaving as expected. There are two approaches to setting up the code so you can examine what is going on.

Sleep

If you need an operation to finish before the next line is executed and you cannot get a notification from the system you are calling, although not perfect, sleep is the best option.

C#
System.Threading.Thread.Sleep(TimeToWaitForSomething);

Make sure you don't put magic numbers, i.e., (5000), as this means nothing, and if the behaviour of the system changes, you can easily change the sleep period in a single place in the code, rather than grepping for 5000 !

Dialog Box

"No", they cried, "What?", they gasped. Yes, sometimes you want to set some hardware up while you are in the middle of a test, for this I use a dialog. I've never checked in code to a repository that does this, every system I have worked on has had a fully automated test suite (i.e., no manual intervention). But, sometimes, for debugging it can be useful, i.e. pair an iphone before downloading an application requires someone to click on a dialog on the iOS device, therefore I need a dialog in the test code.

C#
DialogResult d = MessageBox.Show("Hello", 
	"Press the button when you want to run the app", MessageBoxButtons.OK);

Conclusion

Try and keep your tests atomic (i.e. test one thing), chaining functionality together can give results that always need investigating further. For example, a test installs an app on a phone. It fails. Ok this is single piece of functionality so investigate why.

Another test attempts to install an app, run the app, close it, and then un-install the application. The test fails, ok now we have to debug to find out where it fails (or at least investigate the log). If this test is testing the un-install, then this may be the only way to set the device up to test the un-install, so it's unavoidable. If this isn't the case, then split the test up into individual atomic parts and test them independently.

Desirable test results should indicate exactly what functionality is broken simply by which test fails (goes red).

I have caught 80% more bugs earlier on in the development lifecycle just from using a test-driven approach. It took me years to finally agree to doing it, and now I am using it I can't imagine developing in any other way. Take the plunge!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
Canada Canada
I have a blog here

http://www.electricalengineeringschools.org/blog/

And my robotics web site is here :

http://www.roboticsfordreamers.com/

I have been a software engineer for over 20 years now. I'm an expert in writing scalable restful web API services, which I've been doing for over 7 years now for games companies such as EA and Ubisoft. I've also worked on several video games, including Skate 2 and 3, NHL, Need for Speed, various Assassins Creed games, Far Cry 3 and 4, Driver San Francisco and most recently with a team of 15 on EA's UFC free to play. On this latest project I designed, implemented, tested (including load tests in Gatling and JUnit on AWS), deployed and supported all the web services for the game client, supporting up to a half a million active sessions.

I am proficient at programming in C, C++, C#, Java etc. I've developed many types of games, applications, SDKs and web services in Linux, Unix, iOS, Android and Windows.

My spare time is spent teaching and lecturing computing languages and science. I have a PhD in Artificial Intelligence (specialising in knowledge representation - Semantic networks with inference engines).

Currently I am also building an A.I. general purpose robot. The brains behind it are a mix of 6 Raspberry Pi and Banana Pros, with 2 USB cameras, SATA drive, Router Switch with inter-pi Comms, 2 motorised track systems, plus various sensors including motion/pir/sound etc.

The six pi's are split according to functions, including eyes (image processing/recognition), ears (speech processing and simulated speech),motor (object avoidance, environment mapping, drives all movement), entertainment (web browsing, media playing etc), brain (knowledge system, and adaptive learning/memory systems) and development system ( logging, diagnostics, debugging).

I am available as a consultant, so if you need to get something out the door quick, or want to set down an expandable and resilient framework, ping me !

Comments and Discussions

 
SuggestionExpectedException Attribute Pin
sx200822-Oct-15 9:14
sx200822-Oct-15 9:14 
GeneralRe: ExpectedException Attribute Pin
marcus obrien22-Oct-15 9:49
marcus obrien22-Oct-15 9:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.