Unit testing: getting started

Unit testing is hailed by many as the best way to write and maintain code. Unit tests are written with the help of a unit testing framework. NUnit is the dominating framework in .NET.

namespace SpaceGame
{
    public class Enemy
    {
        private int hitpoints;

        public Enemy(int hitpoints)
        {
            this.hitpoints = hitpoints;
        }

        public bool Alive
        {
            get
            {
                return hitpoints > 0;
            }
        }

        public void Hit(int damage)
        {
            if (damage < 0)
            {
                throw new ArgumentOutOfRangeException("damage");
            }
            hitpoints -= damage;
        }
    }
}

In NUnit tests are contained in its own class. Normally with a test class for each class under test. It is also practical to put all tests in a separate project and assembly.

To run the tests we need a test runner. NUnit has its own runner in the form of a console or GUI program. However, it is much more convenient to use a runner that integrates with Visual Studio. If you already use ReSharper or CodeRush they might be a good choice.

namespace SpaceGame.UnitTests
{
    [TestFixture]
    public class EnemyTests
    {
        private Enemy enemy;

        [SetUp]
        public void Setup()
        {
            enemy = new Enemy(100);
        }

        [Test]
        public void NewEnemy_IsAlive()
        {
            Assert.IsTrue(enemy.Alive);
        }

        [TestCase(100)]
        [TestCase(101)]
        [TestCase(Int32.MaxValue)]
        public void Hit_OnceForMoreThanHitpoints_Kills(int damage)
        {
            enemy.Hit(damage);
            Assert.IsFalse(enemy.Alive);
        }

        [TestCase(0)]
        [TestCase(1)]
        [TestCase(50)]
        [TestCase(99)]
        public void Hit_OnceForLessThanHitpoints_RemainsAlive(int damage)
        {
            enemy.Hit(damage);
            Assert.IsTrue(enemy.Alive);
        }

        [TestCase(-1)]
        [TestCase(Int32.MinValue)]
        public void Hit_OnceForNegativeDamage_ThrowsException(int damage)
        {
            Assert.Catch<ArgumentOutOfRangeException>(() =>
            {
                enemy.Hit(damage);
            });
        }

        [Test]
        [Ignore("Currently fails due to bug 1")]
        public void Hit_TwiceForMaxInteger_Kills()
        {
            enemy.Hit(Int32.MaxValue);
            enemy.Hit(Int32.MaxValue);
            Assert.IsFalse(enemy.Alive);
        }
    }
}

An important goal when writing unit tests is to make them readable. Each test should test for one thing only and tests should not contain any logic of their own. Contrary to regular programming hardcoded values are a good thing when writing tests.

The last part shows how a test can be ignored which may be useful when a bug is uncovered but there isn’t time to fix it yet. This should be used sparingly of course.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s