Basic Unit Testing Guidelines

by KodefuGuru 23. July 2009 16:26

When I recently started looking through a certain project’s tests, I was struck by how difficult it was for me to read and understand. The tests were laid out haphazardly, and the code contained enough logic to make me wonder if it would be easier to analyze the functional code. Tests don’t do anyone good if they require that much analysis. In contrast, one of my favorite open source projects contains tests that allow me to learn the functionality of the system without looking at any functional code.

The intent of this post isn’t to describe advanced unit testing techniques but to describe a few guidelines that I feel should be implemented regardless if you’re mocking objects, using test driven development, or just want to ensure your code works. I am using Microsoft’s unit testing framework, but these same guidelines apply in most other unit testing frameworks.

Name Code Files Sensibly

You should be able to find your test, which may be difficult to do if you name your file “MyTests.cs.” Naming the file after the class you’re testing is a guideline many people use. Alternatively, you can name it after a functional area if you have more than one fixture per file. Yes, generally speaking you should have one class per file, but I admit to relaxing this rule because you are going to experience class explosion if you’re striving for 100% coverage. I prefer to name my class files after the class just as I do in functional development.

Name Fixture After Scenario

Your test fixture should set up a scenario, and the name of the class should reflect that scenario. For example, if you’re testing what happens when a user activates an account, name the class “WhenUserActivatesAccount.”

[TestClass]
public class WhenUserActivatesAccount
{
}

Setup Code in Constructor

Usually, your constructor (or Setup method in some frameworks) should initialize the scenario that you’re testing. If external setup must be done, use the ClassInitializer attribute on a static method.

User user;
Account account;

public WhenUserActivatesAccount()
{
    user = new User("kodefuguru");
    account = user.Account;
    account.Activate();
}

If you expect exceptions with certain parameters in various, similar scenarios; it may be necessary to call the setup code from within the testing method. I prefer to refactor my tests so that the test for the expected exception is in a derived class that tweaks the modifications within its constructor before calling the base constructor… for example, “public class WhenInvalidUserActivatesAccount : WhenUserActivatesAccount.”

Name Test After Expected Result

Ideally, tests contain one Assert statement. You’re testing a specific condition that you expect to occur as a result of the scenario you’ve presented. When a user activates an account, you expect the account to activated. Therefore, your test should be named “AccountActivatedShouldBeTrue,” with the assertion “Assert.IsTrue(account.Activated).”

[TestMethod]
public void AccountActivatedShouldBeTrue()
{
    Assert.IsTrue(account.Activated);
}

No Conditionals Containing Assertions

If assertions are contained within a conditional statement, then you aren’t really ensuring that your code is running as expected. When you’re writing tests, you’re declaring the behavior of your system based upon certain conditions.

[TestMethod]
public void AccountActivatedShouldDependOnUserValid()
{
    if (user.Valid)
    {
        Assert.IsTrue(account.Activated);
    }
    else
    {
        Assert.IsFalse(account.Activated);
    }
}

If I have a test like this, I’m not really declaring a fact about the system. Instead, I’m declaring facts about two different situations, one of which will not occur. Whether user.Valid is true or not should depend on the setup of my test, and I should know what the value will be when this test runs.

Differentiate Between Unit Tests and Integration Tests

Unit tests are for testing an individual unit of code. Integration tests are for testing functionality between different systems. It is best to separate unit tests and integration tests by placing them in separate projects. If you’re not already doing so, at some point you may wish to automate your testing process. In a continuous integration environment, unit tests should be run with every build. Integration tests tend to be slower, and it is oftentimes not practical to automate them as frequently.

If you have trouble differentiating between unit tests and integration tests (i.e. everything you do connects to a service or persists to the database), it would be a good idea to look into mocking and inversion of control so that you can create tests for units of functionality.

---

There are many more things you can do to create effective unit tests, but if you follow the few guidelines I have listed you will benefit immediately. It is important that it is easy for anyone looking at your tests to know how the system is supposed to behave. There is additional benefit in that you can harvest fixture and test names to create functional documentation for your software.

Tags: ,

Kodefu

Comments

7/23/2009 4:27:16 PM #

trackback

Trackback from DotNetKicks.com

Basic Unit Testing Guidelines

DotNetKicks.com

7/23/2009 4:29:45 PM #

trackback

Trackback from DotNetShoutout

Basic Unit Testing Guidelines

DotNetShoutout

7/24/2009 3:41:37 AM #

pingback

Pingback from blog.cwa.me.uk

Reflective Perspective - Chris Alcock  » The Morning Brew #397

blog.cwa.me.uk

7/24/2009 4:06:49 AM #

Benoit guerout

This is some of the principals of BDD (Behaviour-driven development) :

http://behaviour-driven.org/Introduction
http://dannorth.net/introducing-bdd

Benoit guerout France

7/24/2009 7:29:18 AM #

Typemock

We are holding a webinar on implementing unit testing in companies.

blog.typemock.com/.../...testing-live-webinar.html

Typemock United States

7/24/2009 9:09:06 AM #

chris

Benoit, I think BDD is an awesome process. However, I'm trying to slowly push my team to good practices because I am the newest member here. I felt it would be more effective to introduce good practices over time.

Typemock, I registered for your webinar. However, it is at 4am for me so I can't guarantee I will be on.

chris United States

7/26/2009 6:53:56 AM #

Gil Zilberfeld

Hi Chris,

Great stuff.
Regarding the namnig convention for classes after scenarios: I'm sure it works for you, is it also easy to explain to newcomers?
I currently use the fixture per class. The problem begins when you actually make developers think about what their testing, but that's another story Smile

By the way, we have a second webinar on 3pm EDT, it's probably better for you since there will actually be light outside.

Thanks,

Gil Zilberfeld
Typemock

Gil Zilberfeld Israel

7/26/2009 2:35:30 PM #

Urs Enzler

Hi Chris

Nice article. However, I disagree with two point you state:

1) Set-up code in Constructor (or SetUp method)
We made the experience that setting up your test in single set-up method for the whole test fixture (or test class) is very inflexible and cannot be parametrized. You state that in such a scenario, you use derived test fixtures. This solution lacks readability in my opinion. Our solution is that we use the set-up method only for very basic infrastructure initialization (e.g. the mock factory). The test specific set-up is provided by a private method in the same class so that several tests can call it. This method can be parametrized very simply. If you use derived classes then you have to switch classes to read a complete test.

2) integration tests vs. run on every commit/build
Integration tests should be run as often as possible in order to detect defects as soon as possible. The best is to run them on every commit or at least as soon as the last test run was completed and there are changes on the repository. It's easier to spot the cause of a problem if there are only a few commits and not the commits of a whole day.

With the rest of your points, I absolutely agree and I wish that there would be more resources on how to unit test cleanly. I'm currently reading Clean Code by Robert C. Martin but unfortunately the section about unit tests is rather disappointing.

Cheers,
Urs
planetgeek.ch, NMock2, bbv.Common

Urs Enzler Switzerland

7/26/2009 3:47:39 PM #

Chris

Hi, thanks for commenting Urs.

1) It depends on what type of test your setting up. I prefer to test scenarios, which do not really lend themselves to parameterized tests. In situations where I want parameterized tests, putting it in the constructor oftentimes won't work... so your approach is better for those. When I wrote the guidelines, it was in response to every test being responsible for it's own setup. I

2) That should be the case on smaller teams. However, I've worked on large teams where it made it nearly impossible to check anything in because the tests took to long to complete. If the build is running, you don't know whether or not it is good. My rule is 10 minutes - if the build is going longer than that then pull the integration tests from the build procedure. Optionally, you can pull long running integration tests and put them into a separate project that runs less frequently. The unit tests test expected behavior of individual units of code. The integration tests make sure the subsystems and services are connected. I would rather sacrifice the continuous integration of the latter than the former.


I would like to continue to hammer out guidelines and samples for writing clean unit tests. I guess I'll avoid Martin's book though.

Chris United States

7/26/2009 3:50:22 PM #

Chris

Hi Gil,

Regarding the namnig convention for classes after scenarios: I'm sure it works for you, is it also easy to explain to newcomers?

I think when a developer sees it, they get it. I've found that it's hard to break someone out of thinking "I'm testing my class" to "I'm testing a scenario" unless you make it visual. One they see that they can understand what the fixture/test is doing just by reading the name, they come around.

Chris United States

7/27/2009 8:28:09 AM #

trackback

Trackback from #.think.in

#.think.in infoDose #38 (20th July - 27th July)

#.think.in

7/28/2009 6:05:47 AM #

pingback

Pingback from jaceju.net

網站製作學習誌 » [Web] 連結分享

jaceju.net

8/2/2009 5:45:02 AM #

Gil Zilberfeld

Chris,

What I found is that regardless of what the naming includes, if you make the new developer just think about what he's going to test, it's already 3/4 of the way.

For beginners it's more easier for me to start explaining about project structure, rather than scenario ones. It requires less "big picture" view from the developer, and that means he can concentrate on small victories. But to each his own.

Thanks,

Gil

Gil Zilberfeld

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.6.0.0
Theme by Mads Kristensen

Whois KodefuGuru

Chris Eargle

Chris Eargle
.NET Community Champion

LinkedIn Twitter Technorati Facebook

MVP - Visual C#

 

INETA Community Champions
Friend of RedGate
Telerik .NET Ninja
Community blogs & blog posts

I am a #52er


World Map

RecentComments

Comment RSS

Tag cloud

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010