Code Project: Epic Quest! Unit Testing

A staple of TDD (Test Driven Development), although not quite how I’ve implemented it here (as I already have too much code hanging around prior to writing any tests), the humble Unit Test is something I’ve wanted to dedicate a little bit of time to for a while; so here it is.

Inclusion of Unit Tests in a project, from my own personal experience, is something that rarely takes as much precedence as it should. Sometimes they aren’t included at all; other times they are incredibly slim-line to the point of only scratching the surface – This feels like a bit of a shame. I won’t get into my own feelings too much on the matter beyond saying that I’m of the belief that they certainly have a place and add value.

To this end I’ve gone ahead and created a few simple Unit Tests covering some of the foundation classes of the EpicQuest code base as follows:

  • DungeonTests test class – Covering the EpicDungeon class.
  • RoomTests test class – Covering the Room class (and creation of room content).
  • CombatTests test class – Covering a very basic combat scenario which will be fleshed out further in due course.
  • WeaponTests test class – Covering a basic Weapon object creation scenario.
  • ArmourTests test class – Covering a basic Armour object creation scenario.
  • TreasureTests test class – Currently unimplemented (the concept of ‘treasure’ still needs consideration).

As a bit of a visual aid, here’s a snippet from Visual Studio showing the Unit Test project structure and a Class Diagram illustrating the setup:

Unit Test Project Configuration.
Unit Test Project Configuration.
Unit Test Class Diagram.
Unit Test Class Diagram.

One of key things to notice here is that the EpicQuest project has been referenced in order for us to get hold of the types we need to test. A key benefit I’ve gained since inclusion of the tests is that I’ve been forced to compartmentalise my code further to make testing distinct pieces of functionality possible. Some code has also been re-homed fully (i.e. whereby I had methods sitting within classes whereby it didn’t actually make sense for them to live there from a code structure point of view and, therefore, a Unit Testing perspective). A few difficult questions have also been posed in relation to the accessibility of certain members during creation of the test code; issues I wouldn’t have had to consider without travelling down this path. All in all, pretty positive outcomes so far.

So what’s the anatomy of a basic Unit Test, how is it triggered and how can we glean valuable information from the results?

Within Visual Studio at least, the first requirement is a Unit Test project, as the previous screen shot illustrates (EpicQuest.Tests). From here, a great place to begin is by adding a Basic Unit Test template class:

Add New Unit Test Dialog.
Add New Unit Test Dialog.

This will provide the following stub class adorned with the TestClass attribute with a single test method, suitably adorned with the TestMethod attribute. These attributes identify the object/members as test entities for exposure in the Test Explorer window which we’ll see later.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace EpicQuest.Tests
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {

        }
    }
}

Test methods, regardless of the functionality being tested, will normally follow a structure that looks like this:

/// <summary>
/// Test method that ensures that the EpicDungeon
/// instance level RemoveDungeonRoom method is working
/// as expected.
/// </summary>
[TestMethod]
public void EpicDungeonRoomRemovalTest()
{
	//Phase one - Arrange (essentially complete any configuration to actually perform the test - In this case 
	//create a dungeon and add a known room instance to it, taking note of the room count prior to removal of the room)
	Room roomToRemove = new Room();

	EpicDungeon dungeon = new EpicDungeon(EpicDungeon.GenerateRandomRoomConfiguration()); //Opted to create a random room configuration to strengthen the test scenario
	dungeon.AddDungeonRoom(roomToRemove);

	int previousRoomCount = dungeon.DungeonRooms.Count;

	//Phase two - Test/Act (remove the room)
	dungeon.RemoveDungeonRoom(roomToRemove);

	//Phase three - Test/Assert (run tests to ensure that the correct end result, after removal of the room, has been achieved)
	Assert.IsFalse(dungeon.DungeonRooms.Contains(roomToRemove), "EpicDungeonRoomRemovalTest (DungeonTests) failed. Test room roomToRemove still found in dungeon.DungeonRooms.");
	Assert.AreEqual<int>((previousRoomCount - 1), dungeon.DungeonRooms.Count, "EpicDungeonRoomRemovalTest (DungeonTests) failed. Room count (dungeon.DungeonRooms.Count) has an unexpected value.");
}

The structure factors in three distinct phases:

  • Arrange. This stage involves configuring any objects and setup as required by the test you plan to perform.
  • Act. Post configuration you actually need to call the functionality you wish to test.
  • Test. Lastly, assertions are run in order to see if the code under test worked as expected.

The MSDN documentation surrounding running Unit Tests through Visual Studio is well written and quite comprehensive. Further information can be found by accessing the following link:

Walkthrough: Creating and Running Unit Tests for Managed Code.

In order to see if the code under test is working as expected the static members of the Assert class come into play (of which there are a few, see the Assert Class MSDN documentation for a full listing).

In the above listing, I’ve opted to firstly use Assert.IsFalse to make a logical test to see if the Room instance I am expecting to be removed by the RemoveDungeonRoom method has actually be taken out of the EpicDungeon instance. Lastly, I’ve called on a generic version (which is awesome!) of Assert.AreEqual to make a secondary check to ensure the remaining count of Room objects in the EpicDungeon is as I would expect, following the previous Room object removal. The Assert static members allow for value types and objects to be passed in for inspection and, in the instances I’ve seen, for a string message to be specified; to be shown if the test fails. You’ll see other examples of Assert methods in action, such as the Assert.IsInstanceOfType, in later examples.

The Assert class and the members on offer are fairly intuitive right off the bat; you’ll probably only need a few minor peeks at the documentation to get underway if you’re familiar with C#.

NOTE: Some of the xml comments and variable names, etc have been tweaked throughout the code at varying times meaning some of the screen shots you see may fall out of line slightly with the code samples. If something is functionally different then I have gone back and altered the screen shots to align with the code.

As there’s not tonnes of code as of yet in the tests I’ve written here’s the full listing of what I have so far:

Dungeon Tests

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Models.Dungeon;
using System.Collections.Generic;

namespace EpicQuest.Tests.Dungeon
{
    /// <summary>
    /// Dungeon entity specific Unit Tests
    /// for the Epic Quest game.
    /// </summary>
    [TestClass]
    public class DungeonTests
    {
        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// object (static) level GenerateRandomRoomConfiguration method 
        /// is working as expected. 
        /// </summary>
        [TestMethod]
        public void EpicDungeonRoomGenerationTest()
        {
            //Generate a random room amount seed and get an actual number of rooms as generated by EpicDungeon.GenerateRandomRoomConfiguration. Obtain the difference
            int roomNumberSeed = new Random().Next(3, 10), roomNumber = EpicDungeon.GenerateRandomRoomConfiguration(roomNumberSeed).Count,
                difference = (roomNumber - roomNumberSeed);

            //Extra debugging
            System.Diagnostics.Trace.WriteLine(string.Format("EpicDungeonRoomGenerationTest roomNumberSeed {0}; roomNumber: {1}; difference: {2}.", roomNumberSeed, roomNumber, difference));

            //The test is successful if the difference is between 0 and 2 (these are the rules that I've specified)
            if (difference < 0 || difference > 2)
            {
                Assert.Fail("EpicDungeonRoomGenerationTest (DungeonTests) failed. Room difference between seed number and actual, generated number out of range. Difference: {0}", difference);
            }
        }

        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// object constructor is working
        /// as expected.
        /// </summary>
        [TestMethod]
        public void EpicDungeonCreationTestOne()
        {
            //Get a predefined list of rooms and pass them into an EpicDungeon object constructor
            List<Room> testRooms = new List<Room>();
            testRooms.AddRange(new Room[3] { new Room(), new Room(), new Room() });

            EpicDungeon dungeon = new EpicDungeon(testRooms);

            //Test to ensure that dungeon.DungeonRooms is based on our testRooms collection
            Assert.AreEqual(testRooms, dungeon.DungeonRooms, "EpicDungeonCreationTestOne (DungeonTests) failed. Test room array is not represented by dungeon.DungeonRooms (different refs).");
        }

        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// object constructor is working
        /// as expected (very basic test).
        /// </summary>
        [TestMethod]
        public void EpicDungeonCreationTestTwoBasic()
        {
            //Test the constructor that takes an array of room objects
            Room[] roomArray = new Room[] { new Room(), new Room(), new Room() };

            EpicDungeon dungeon = new EpicDungeon(roomArray);

            //The room array is converted into a list within the Room class - Do a simple count check as a follow up to EpicDungeonCreationTestOne
            Assert.AreEqual<int>(roomArray.Length, dungeon.DungeonRooms.Count, "EpicDungeonCreationTestTwoBasic (DungeonTests) failed. Test array room count differs to dungeon.DungeonRooms.Count.");
        }

        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// instance level AddDungeonRoom method is working
        /// as expected.
        /// </summary>
        [TestMethod]
        public void EpicDungeonRoomAdditionTest()
        {
            EpicDungeon dungeon = new EpicDungeon(EpicDungeon.GenerateRandomRoomConfiguration());
            
            int previousRoomCount = dungeon.DungeonRooms.Count;

            Room roomToAdd = new Room();
            dungeon.AddDungeonRoom(roomToAdd);

            Assert.IsTrue(dungeon.DungeonRooms.Contains(roomToAdd), "EpicDungeonRoomAdditionTest (DungeonTests) failed. Test Room roomToAdd not found in dungeon.DungeonRooms.");
            Assert.AreEqual<int>((previousRoomCount + 1), dungeon.DungeonRooms.Count, "EpicDungeonRoomAdditionTest (DungeonTests) failed. dungeon.DungeonRooms.Count has an unexpected value.");
        }

        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// instance level RemoveDungeonRoom method is working
        /// as expected.
        /// </summary>
        [TestMethod]
        public void EpicDungeonRoomRemovalTest()
        {
            //Phase one - Arrange (essentially complete any configuration to actually perform the test - In this case 
            //create a dungeon and add a known room instance to it, taking note of the room count prior to removal of the room)
            Room roomToRemove = new Room();

            EpicDungeon dungeon = new EpicDungeon(EpicDungeon.GenerateRandomRoomConfiguration()); //Opted to create a random room configuration to strengthen the test scenario
            dungeon.AddDungeonRoom(roomToRemove);

            int previousRoomCount = dungeon.DungeonRooms.Count;

            //Phase two - Test/Act (remove the room)
            dungeon.RemoveDungeonRoom(roomToRemove);

            //Phase three - Test/Assert (run tests to ensure that the correct end result, after removal of the room, has been achieved)
            Assert.IsFalse(dungeon.DungeonRooms.Contains(roomToRemove), "EpicDungeonRoomRemovalTest (DungeonTests) failed. Test room roomToRemove still found in dungeon.DungeonRooms.");
            Assert.AreEqual<int>((previousRoomCount - 1), dungeon.DungeonRooms.Count, "EpicDungeonRoomRemovalTest (DungeonTests) failed. Room count (dungeon.DungeonRooms.Count) has an unexpected value.");
        }
    }
}

Room Tests

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Models.Dungeon;
using EpicQuest.Interfaces;
using EpicQuest.Models;
using System.Collections.Generic;
using System.Linq;
using EpicQuest.Models.Items;

namespace EpicQuest.Tests.Rooms
{
    /// <summary>
    /// Room entity specific Unit Tests
    /// for the Epic Quest game.
    /// </summary>
    [TestClass]
    public class RoomTests
    {
        /// <summary>
        /// Test method that ensures that the Room
        /// object (static) level GenerateRandomRoomContent method 
        /// is working as expected. 
        /// </summary>
        [TestMethod]
        public void RoomContentGenerationTest()
        {
            //Test that Room.GenerateRandomRoomContent is producing content based on the seed provided
            int roomContentSeed = 2, roomContentNumber = Room.GenerateRandomRoomContent(roomContentSeed).Count, difference = (roomContentNumber - roomContentSeed);

            //Produce some extra debugging output
            System.Diagnostics.Trace.WriteLine(string.Format("RoomContentGenerationTest (RoomTests) roomContentSeed {0}; roomContentNumber: {1}; difference: {2}.", 
                roomContentSeed, roomContentNumber, difference));

            //Ensure the room content number produced is within the expected range (based on the seed)
            if (difference < 0 || difference > 2)
            {
                Assert.Fail("RoomContentGenerationTest (RoomTests) failed. Room content difference between seed number and actual, generated number out of range. Difference: {0}", difference);
            }
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// object constructor is working as expected.
        /// </summary>
        [TestMethod]
        public void RoomContentCreationTestOne()
        {
            //Add some content to a list of type IRoomContent and pass this to the Room constructor
            List<IRoomContent> roomContentList = new List<IRoomContent>();
            roomContentList.AddRange(new IRoomContent[] { new Skeleton(), new Skeleton(), new Kobold(), new Kobold() });

            Room room = new Room(roomContentList);

            //Check to ensure that the content in the newly created Room object is based on the information passed to the constructor
            Assert.AreEqual(roomContentList, room.RoomFeatures, "RoomContentCreationTestOne (RoomTests) failed. Test room content array is not represented by room.RoomFeatures (different refs).");
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// object constructor is working
        /// as expected (very basic test).
        /// </summary>
        [TestMethod]
        public void RoomContentCreationTestTwoBasic()
        {
            //Chuck an array of IRoomContent to the Room constructor
            IRoomContent[] roomContent = new IRoomContent[] { new Kobold(), new Skeleton() };

            Room room = new Room(roomContent);

            //Again, similar to the Dungeon test of a similiar nature, do a simple count of content items and ensure it's correct
            Assert.AreEqual<int>(roomContent.Length, room.RoomFeatures.Count, "RoomContentCreationTestTwoBasic (RoomTests) failed. Test array room Content count differs to room.RoomFeatures.Count.");
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// instance level AddContentToRoom method is working
        /// as expected.
        /// </summary>
        [TestMethod]
        public void RoomContentAdditionTest()
        {
            //Create a new room and added random content (as base content). Take a note of the items of content currently in the room
            Room room = new Room(Room.GenerateRandomRoomContent());

            int currentRoomFeatureCount = room.RoomFeatures.Count;

            //Add a new monster to the room
            IRoomContent kobold = new Kobold(); 
            room.AddContentToRoom(kobold);

            //Ensure the monster has correctly been added to the room
            Assert.IsTrue(room.RoomFeatures.Contains(kobold), "RoomContentAdditionTest (RoomTests) failed. Test room feature kobold not found in room.RoomFeatures.");
            Assert.AreEqual<int>((currentRoomFeatureCount + 1), room.RoomFeatures.Count, "RoomContentAdditionTest (RoomTests) failed. Room feature count (room.RoomFeatures.Count) has an unexpected value.");
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// instance level RemoveContentFromRoom method is working
        /// as expected. 
        /// </summary>
        [TestMethod]
        public void RoomContentRemovalTest()
        {
            //Add a new skeleton to a new room (with some other random content to strengthen the test example). Take a note of the count of room features prior to calling RemoveContentFromRoom
            IRoomContent skeleton = new Skeleton();

            Room room = new Room(Room.GenerateRandomRoomContent());
            room.AddContentToRoom(skeleton);

            int previousRoomFeatureCount = room.RoomFeatures.Count;

            //Run the test, remove the skeleton from the room
            room.RemoveContentFromRoom(skeleton);

            //Ensure that the known skeleton instance has been removed from the room (and that the count of remaining pieces of room content is correct)
            Assert.IsFalse(room.RoomFeatures.Contains(skeleton), "RoomContentRemovalTest (RoomTests) failed. Test room feature skeleton still found in room.RoomFeatures.");
            Assert.AreEqual<int>((previousRoomFeatureCount - 1), room.RoomFeatures.Count, "RoomContentRemovalTest (RoomTests) failed. Room feature count (room.RoomFeatures.Count) has an unexpected value.");
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// instance level RoomClearedOfMonstersTest method 
        /// is working as expected. 
        /// </summary>
        [TestMethod]
        public void RoomClearedOfMonstersTest()
        {
            //Configure some fixed, known pieces of room content and add it to a new room
            IRoomContent skeleton = new Skeleton(), kobold = new Kobold(), treasureItem = new TreasureItem();

            Room room = new Room(skeleton, kobold, treasureItem);

            //Test that the room contains monsters at this point
            Assert.IsFalse(room.RoomClearedOfMonsters, "RoomClearedOfMonstersTest (RoomTests) failed. No monsters detected in the room.");

            //Remove just the fixed, known monsters - Leaving the treasure item intact
            room.RemoveContentFromRoom(skeleton, kobold);

            //Final round of tests. Ensure all monsters have been removed and that only one piece of content remains (the treasure item)
            Assert.IsTrue(room.RoomClearedOfMonsters, "RoomClearedOfMonstersTest (RoomTests) failed. Monsters detected in the room.");
            Assert.AreEqual<int>(room.RoomFeatures.Count, 1, "RoomClearedOfMonstersTest (RoomTests) failed. An unexpected room feature count was detected (room.RoomFeatures.Count).");
            Assert.IsInstanceOfType(room.RoomFeatures.First(), typeof(TreasureItem), "RoomClearedOfMonstersTest (RoomTests) failed. Remaining room feature is not of the expected type. Type detected: {0}",
                room.RoomFeatures.First().GetType().Name);
        }
    }
}
 

Combat Tests

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Interfaces;
using EpicQuest.Models;
using EpicQuest.Utility;

namespace EpicQuest.Tests.Combat
{
    /// <summary>
    /// Combat based entity specific Unit Tests
    /// for the Epic Quest game. 
    /// </summary>
    [TestClass]
    public class CombatTests
    {
        /// <summary>
        /// Test method that ensures that the StaticGameActions
        /// object (static) level HandleCombat method 
        /// is working as expected.  
        /// </summary>
        [TestMethod]
        public void CombatTestOne()
        {
            //Setup the aggressor and defender ICombatant supporting objects
            ICombatant aggressor = new Brawler(), defender = new Skeleton();

            //Handle the combat. Take the defenders previous health and obtain the damage done from StaticGameActions.HandleCombat call
            int defenderPreviousHealth = defender.Health, damageDone = StaticGameActions.HandleCombat(aggressor, defender);

            //Test that the defenders health has been subtracted properly (TODO - Handle dead defenders and make sure this works if a defender is overkilled)
            Assert.AreEqual<int>((defenderPreviousHealth - damageDone), defender.Health);
        }
    }
}

Weapon Tests

using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Models;
using EpicQuest.Models.Dice;

namespace EpicQuest.Tests.Items
{
    /// <summary>
    /// Weapon entity specific Unit Tests
    /// for the Epic Quest game. 
    /// </summary>
    [TestClass]
    public class WeaponTests
    {
        /// <summary>
        /// Test method that ensures that the Weapon
        /// object constructor is working as expected.
        /// </summary>
        [TestMethod]
        public void WeaponTestBasic()
        {
            //Create a new Weapon
            Weapon weaponItem = new Weapon(GameEnums.HeroOffensiveItemType.LegendaryBastardSword);

            //Ensure that the die associated with the object are as expected
            Assert.IsTrue((weaponItem.Dice.Where(die => die is BlueDie).Count() == 1 && weaponItem.Dice.Where(die => die is RedDie).Count() == 1
                && weaponItem.Dice.Count == 2), "WeaponTestBasic (WeaponTests) failed. Incorrect type of die/die count detected.");
        }
    }
}

Armour Tests

using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Models;
using EpicQuest.Models.Dice;

namespace EpicQuest.Tests.Items
{
    /// <summary>
    /// Armour entity specific Unit Tests
    /// for the Epic Quest game. 
    /// </summary>
    [TestClass]
    public class ArmourTests
    {
        /// <summary>
        /// Test method that ensures that the Armour
        /// object constructor is working as expected. 
        /// </summary>
        [TestMethod]
        public void ArmourTestBasic()
        {
            //Die property is publically accessible??? TO CHECK; Either way create a new, known armour type
            Armour armourPiece = new Armour() { DefensiveItemType = GameEnums.HeroDefensiveItemType.LeatherChestPiece, DefensiveItemTypeSlot = GameEnums.HeroDefensiveItemSlotType.Chest };

            //Ensure the die for this piece of armour are correct
            Assert.IsTrue((armourPiece.Dice.Where(die => die is GreenDie).Count() == 2 && armourPiece.Dice.Count == 2), "ArmourTestBasic (ArmourTests) failed. Incorrect type of die/die count detected.");
        }
    }
}

Treasure Item Tests

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace EpicQuest.Tests.Items
{
    /// <summary>
    /// TreasureItem entity specific Unit Tests
    /// for the Epic Quest game. 
    /// </summary>
    [TestClass]
    public class TreasureTests
    {
        /// <summary>
        /// TO DO.
        /// </summary>
        [TestMethod]
        public void TreasureTestBasic()
        {
            //TO DO, Treasure not fully implemented
        }     
    }
}

So how do you run these tests? It’s pretty damn easy – You just make use of the Test Explorer window in Visual Studio. A massive plus for me is that Unit Test projects, class templates and the Test Explorer are all available with the express edition of Visual Studio; big win! Either type ‘Test Explorer’ into the Quick Launch search bar in the top right hand corner of the Visual Studio UI or navigate to TEST > Windows > Test Explorer on the Visual Studio menu bar:

Accessing the Test Explorer Window.
Accessing the Test Explorer Window.

The Test Explorer window will automatically detect projects in scope that contain members marked with the TestMethod attribute (the classes of course need the TestClass attribute to). The Test Explorer window allows you to run all tests, previously failed tests, individual tests or a specific playlist of tests. The Test Explorer will appear like this in its initial state:

The Test Explorer Window (Initial State).
The Test Explorer Window (Initial State).

Here’s what the Test Explorer looks like after a successful test run. For starters, you get a run-down of how long each test took to run along with a link taking you to the location of the test method in the source code. The image shows an example of some additional debugging output:

Test Additional Output Example.
Test Additional Output Example.

In order to get hold of extra debugging output you can use the Trace.WriteLine static method as indicated in the following code snippet:

/// <summary>
/// Test method that ensures that the Room
/// object (static) level GenerateRandomRoomContent method 
/// is working as expected. 
/// </summary>
[TestMethod]
public void RoomContentGenerationTest()
{
	//Test that Room.GenerateRandomRoomContent is producing content based on the seed provided
	int roomContentSeed = 2, roomContentNumber = Room.GenerateRandomRoomContent(roomContentSeed).Count, difference = (roomContentNumber - roomContentSeed);

	//Produce some extra debugging output
	System.Diagnostics.Trace.WriteLine(string.Format("RoomContentGenerationTest (RoomTests) roomContentSeed {0}; roomContentNumber: {1}; difference: {2}.", 
		roomContentSeed, roomContentNumber, difference));

	//Ensure the room content number produced is within the expected range (based on the seed)
	if (difference < 0 || difference > 2)
	{
		Assert.Fail("RoomContentGenerationTest (RoomTests) failed. Room content difference between seed number and actual, generated number out of range. Difference: {0}", difference);
	}
}

Let’s rig a failure. I’ve simulated an underlying change to the code base that is going to trip up some of our Unit Tests. We receive the following output in the Test Explorer window after running all tests:

Test Explorer Failure Example.
Test Explorer Failure Example.

Two of our test methods fell over and a quick look at the output suggests that the Room class AddContentToRoom instance level method appears to be functioning unexpectedly (the room feature is not being added to the room). This is the state of the source code as it currently stands:

/// <summary>
/// Public method that allows a piece of content/multiple pieces
/// of content to be added to this particular Room.
/// </summary>
/// <param name="features">A single piece of content/array of content pieces to add to the Room.</param>
public void AddContentToRoom(params IRoomContent[] features)
{
	if (features != null && features.Count() > 0)
	{
		foreach (IRoomContent item in features)
		{
			roomFeatures.Add(new Kobold());
		}
	}
}

Looking through this code we can see that the method accepts an array of type IRoomContent named features. After a null check and a count to ensure the array is not empty we enter a simple foreach statement to add the IRoomContent supporting objects to the roomFeatures data field. The roomFeatures.Add statement is however simply adding a new Kobold object to the roomFeatures field list and ignoring the items in the array being passed in. Bad bug; slap on the wrists for introducing that!!! Let’s alter it to this:

/// <summary>
/// Public method that allows a piece of content/multiple pieces
/// of content to be added to this particular Room.
/// </summary>
/// <param name="features">A single piece of content/array of content pieces to add to the Room.</param>
public void AddContentToRoom(params IRoomContent[] features)
{
	if (features != null && features.Count() > 0)
	{
		foreach (IRoomContent item in features)
		{
			roomFeatures.Add(item);
		}
	}
}

Now we are using the foreach item variable correctly and adding this to the roomFeatures collection.

Back in the Test Explorer we are now able to explicitly re-run just the failed tests as shown:

Failed Tests in the Test Explorer Window.
Failed Tests in the Test Explorer Window.

The failed tests run up and we can see that these tests run without error, happy days!

Passed Tests in the Test Explorer Window.
Passed Tests in the Test Explorer Window.

As for Unit Testing, that’s about it for now. The only other small alteration I’ve made to the existing code base at this time is removal of the HandleCombat method from the GameManager class (to aid in Unit Testing/general code structure). This now resides in a new static class called StaticGameActions.

using System;
using EpicQuest.Interfaces;

namespace EpicQuest.Utility
{
    /// <summary>
    /// Public static utility class for the
    /// EpicDungeon game.
    /// </summary>
    public static class StaticGameActions
    {
        /// <summary>
        /// Private method that shows, in principle, how combat
        /// can be handled between two ICombatant based objects.
        /// </summary>
        /// <param name="aggressor">The instigator of the combat represented by a class implementing ICombatant.</param>
        /// <param name="target">The defender represented by a class implementing ICombatant.</param>
        public static int HandleCombat(ICombatant aggressor, ICombatant target)
        {
            //Prepare values representing the dice (random) and values to keep a tally of the offence, defence and difference totals
            int damageTotal = 0, defenceTotal = 0, difference = 0;
            Random rollDie = new Random();

            //Roll attack dice for the aggressor and get a damage total
            aggressor.AttackDice.ForEach(attackDie =>
            {
                damageTotal += (byte)attackDie.DieFaces[rollDie.Next(0, 5)];
            });

            //Roll defence dice for the defencder and get a defence total
            target.DefenceDice.ForEach(defenceDie =>
            {
                defenceTotal += (byte)defenceDie.DieFaces[rollDie.Next(0, 5)];
            });

            //Determine the difference between the damage/defence totals and deal damage to the target if required
            difference = damageTotal - defenceTotal;

            if (difference > 0)
            {
                target.Health -= difference;
            }

            return difference < 0 ? 0 : difference; //Only return non-negative results
        }
    }
}

That’s about it folks for the time being. Unit Testing is an interesting topic that requires some further investigation so I want to bring you another small piece looking at the TestInitialize and TestCleanup attributes and how more in depth tests can be constructed.

In other news, I’ve upgraded my blog account and got a new domain which I’m sure you’ll notice (splashed some cash as promised!). I’ll shortly be, as stated, making a few styling tweaks to the theme in addition to adding a dedicated Facebook page and Twitter feed, so watch this space.

Ciao for now.

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 )

Facebook photo

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

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.