Learning Unit Testing XI – Difficult Terrain Is Difficult

As the title clearly states, difficult terrain is going to be hard. It will involve a replacement of the ‘get distance’ code that takes into account that strict distances are not the same as movement distances – the idea that four inches of distance may cost five, six or more inches of movement (which is what counts for running, charging, and moving in general).

Because of this difficulty, my current inability to devise a solution, and my need to get moving on other things immediately (time on this project is limited, and I would rather move on than get stuck in design hell that maybe I should have sorted out sooner) I will be abandoning the Movement phase for the infinitely more interesting Shooting phase. It actually looks slightly easier, but I’m sure that’ll be remedied when I get to the rules for weapons and skills.

Unlike movement, which started to bog down the Model class, I’m going to try and keep all shooting related code in the shooting manager. Again, having an inkling of what the skills and weapons rules are going to do later, I believe this will make things a lot easier.

The first thing written down is defining who can shoot – Any model can shoot as long as it’s armed with a missile weapon, is not in hand-to-hand combat, can see a target, didn’t run or charge and is not hiding. This seems like a sensible thing to write a checking method for, and a little raft of tests. Time for a new test class, and also (finally) a test helper.

class TestHelper
{
public static IGameManager GetMockGameManager(IModel testModel, IObjectManager objectManager, int numberOfEnemies, int numberOfAllies)
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
List<IModel> modelList = new List<IModel>();
modelList.Add(testModel);
for (int i = 0; i < numberOfAllies; i++)
{
modelList.Add(new Model(testModel.Player, mockGameManager.Object, objectManager));
}
for (int i = 0; i < numberOfEnemies; i++)
{
modelList.Add(new Model(testModel.Player + 1, mockGameManager.Object, objectManager));
}
mockGameManager.Setup(item => item.Models).Returns(modelList);
return mockGameManager.Object;
}
}
[TestClass]
public class ShootingManagerTests
{
[TestMethod]
public void CanShoot_ReturnsTrue_IfEverythingCorrect()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsTrue(shootingTest.CanShoot(model));
}
}

Right, a lot of ‘generate class’ and ‘generate stub’ later (the ctrl+. shortcut is my friend) and the library compiles again. I intend for all missile weapons (that need special functionality not covered by the basic rules) to inherit from MissileWeapon, which ought to cover the majority of things. Weapons is a list of IWeapon (implemented by MissileWeapon), Direction is just an integer describing the angle the model faces (assuming a top-down view), and ShootingManager is quite an empty class at the moment. Following TDD pretty strictly, to pass this test it becomes:

public class ShootingManager
{
public bool CanShoot(Model model)
{
bool canShoot = true;
return canShoot;
}
}

But of course, that’s not quite right. Here’s a few more tests.

[TestMethod]
public void CanShoot_ReturnsFalse_IfRunning()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = true;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}
[TestMethod]
public void CanShoot_ReturnsFalse_IfCharging()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = true;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}
[TestMethod]
public void CanShoot_ReturnsFalse_IfHiding()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = true;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

And what do all those lines of code need to make them pass?

public bool CanShoot(Model model)
{
bool canShoot = true;
if (model.IsRunning == true || model.IsHiding == true || model.IsCharging == true)
{
canShoot = false;
}
return canShoot;
}

It’s almost insulting. The next one should be a bit more of a challenge:

[TestMethod]
public void CanShoot_ReturnsFalse_IfNoMissileWeapon()
{
Model model = new Model(1, null, null);
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}
[TestMethod]
public void CanShoot_ReturnsFalse_IfOnlyMeleeWeapons()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MeleeWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

And these are solved by:

public bool CanShoot(Model model)
{
bool canShoot = true;
if (model.IsRunning == true || model.IsHiding == true || model.IsCharging == true)
{
canShoot = false;
}
if (model.Weapons.Where(item => (item as MissileWeapon) != null).Count() == 0)
{
canShoot = false;
}
return canShoot;
}

Linq makes things a little bit too easy these days… The next test checks if the model can see an opponent:

[TestMethod]
public void CanShoot_ReturnsFalse_IfNoEnemyInSight()
{
Model model = new Model(1, null, null);
model.Weapons.Add(new MissileWeapon());
TestHelper.GetMockGameManager(model, null, 1, 0);
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 0;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

As expected, it fails. The only difference to the original ‘everything is fine’ test case is the direction that the model is facing – a model can only see in a 90 degree arc to it’s front, so by turning it completely around it should be unable to see the enemy properly. The calculations on the abstract ObjectManager should reflect this, so an extra method in ObjectManager that is exposed (slightly) through Model will help out.

First off – a lot of tests break. This is because the models have been set up with null object managers, which obviously throw exceptions when the call to check them for models in sight occurs. In refactoring to get the first test (pass if all clear) passing, references and parameters were shifted from Model to IModel, things were added to IModel,and in general a bit of housekeeping took place. Here is the new ‘all clear’ test:

[TestMethod]
public void CanShoot_ReturnsTrue_IfEverythingCorrect()
{
IModel model = new Model(0, null, null);
IGameManager gameManager = TestHelper.GetMockGameManager(model, null, 1, 0);
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockObjectManager.Setup(item => item.GetModelsInSight(It.IsAny<IModel>(), gameManager.Models, 90)).Returns(gameManager.Models);
model = new Model(1, gameManager, mockObjectManager.Object);
model.Weapons.Add(new MissileWeapon());
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsTrue(shootingTest.CanShoot(model));
}

With similar ‘setup’ code to the subsequent tests to get them all passing. The improved ‘new’ test:

[TestMethod]
public void CanShoot_ReturnsFalse_IfNoEnemyInSight()
{
IModel model = new Model(0, null, null);
IGameManager gameManager = TestHelper.GetMockGameManager(model, null, 1, 0);
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockObjectManager.Setup(item => item.GetModelsInSight(It.IsAny<IModel>(), gameManager.Models, 90)).Returns(new List<IModel>());
model = new Model(1, gameManager, mockObjectManager.Object);
model.Weapons.Add(new MissileWeapon());
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 0;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

And finally, the code to pass it:

public bool CanShoot(IModel model)
{
bool canShoot = true;
if (model.IsRunning == true || model.IsHiding == true || model.IsCharging == true)
{
canShoot = false;
}
if (model.Weapons.Where(item => (item as MissileWeapon) != null).Count() == 0)
{
canShoot = false;
}
if ((from target in model.GetModelsInSight(90)
where target.Player != model.Player
select target).Count() == 0)
{
canShoot = false;
}
return canShoot;
}

With one small addition to the Model class:

public List<IModel> GetModelsInSight(int angleOfSight)
{
List<IModel> modelsInSight = new List<IModel>();
modelsInSight = _objectManager.GetModelsInSight(this, _gameManager.Models, angleOfSight);
return modelsInSight;
}

Finally, one more test and the ‘who can shoot’ question is completely answered. Models cannot shoot if they’re already in hand-to-hand combat. A simple boolean property (the exact circumstances of being able to set it will be complicated, but that’s a problem for later) on the Model and IModel, adding it as an explicit ‘false’ parameter in the other CanShoot tests, and it’s own test case:

[TestMethod]
public void CanShoot_ReturnsFalse_IfModelInHandToHandCombat()
{
IModel model = new Model(0, null, null);
IGameManager gameManager = TestHelper.GetMockGameManager(model, null, 1, 0);
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockObjectManager.Setup(item => item.GetModelsInSight(It.IsAny<IModel>(), gameManager.Models, 90)).Returns(gameManager.Models);
model = new Model(1, gameManager, mockObjectManager.Object);
model.Weapons.Add(new MissileWeapon());
model.Location = new LocationPoint(0, 1, 0);
model.Direction = 180;
model.IsCharging = false;
model.IsHiding = false;
model.IsRunning = false;
model.IsInHandToHandCombat = true;
ShootingManager shootingTest = new ShootingManager();
Assert.IsFalse(shootingTest.CanShoot(model));
}

And the complete CanShoot code to pass it:

public bool CanShoot(IModel model)
{
bool canShoot = true;
if (model.IsRunning == true || model.IsHiding == true || model.IsCharging == true || model.IsInHandToHandCombat == true)
{
canShoot = false;
}
if (model.Weapons.Where(item => (item as MissileWeapon) != null).Count() == 0)
{
canShoot = false;
}
if ((from target in model.GetModelsInSight(90)
where target.Player != model.Player
select target).Count() == 0)
{
canShoot = false;
}
return canShoot;
}

And it leaves me at a good point to finish off for this section. The next section will be the Closest Target rules, which I’ve already given some thought to – but my word count here is already over 1500, so it’s probably best to clock off!

Learning Unit Testing X – Finishing Hiding

Last time, I got the tests for Charging out of the way, and got through a raft of simple checks for Hiding. This time, a few of the more complicated checks.

First off, a model can move around while hiding as long as it remains out of sight. I’m not sure how to be checking every point along the move, but I can check at the end of the movement whether it’s still out of sight.

[TestMethod]
public void Hide_ThrowsExceptionIfInSightAfterMove()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
bool exceptionThrown = false;
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Hide();
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(1);
Assert.IsTrue(testModel.IsHiding);
try
{
testModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
exceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsTrue(exceptionThrown);
Assert.IsFalse(testModel.IsHiding);
}

The best way to check this is probably to call the Hide method at the end of MoveModel – any change in the model’s visibility will be picked up. Code re-use: excellent!

if (this.IsHiding)
{
this.Hide();
}

But I still don’t get a pass… I also need to set IsHiding to false in the Hide method, when it throws an exception. Then the test goes green.

The next test is that after an enemy model has moved, it might force a model to stop being hidden. I will keep the current action of throwing an exception – this will let the imaginary UI show that hiding has ceased.

[TestMethod]
public void Hide_StopsHidingIfInSightAfterEnemyMove()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Movement = 4;
bool exceptionThrown = false;
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Hide();
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(1);
Assert.IsTrue(testModel.IsHiding);
try
{
enemyModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
exceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsTrue(exceptionThrown);
Assert.IsFalse(testModel.IsHiding);
}

The code to pass this test should be just adding the following check to the end of MoveModel (as long as Hide is added to the IModel interface).

foreach (IModel enemyModel in (from models in _gameManager.Models
where models.Player != this.Player
select models))
{
enemyModel.Hide();
}

But this doesn’t work – not only does it not work, but it breaks other tests! Now, Charge_SetsIsChargingFlag, MoveModel_CannotMoveFurtherThanModelsMovement and NewTurn_SetsIsChargingToFalse throw exceptions and this test fails on the penultimate assert that the exception was thrown. I believe the error with this test is that the Mock GameManager I’m using is only returning the enemyModel – not the testModel. When I added the testModel to the setup, this test at least passes. The other tests all throw exceptions. This is probably due to the fact that the GameManager is not being correctly set up in each case, to return an empty List<IModel> – as a mock object, it returns null instead and the above code to check for other models fails when passed a null.

By adding in the required setup line to each of those tests, I get 31 passes again.

Finally, a model is automatically forced out of hiding if an enemy model is within it’s Initiative range of the hiding model. This one needs to be checked when the model chooses to hide, and also whenever it moves or the enemy model moves. Since all three of these cause the Hide method to be called, that should probably be where it sits. The following tests should cover off those three situations:

[TestMethod]
public void Hide_CannotHideIfEnemyInInitiativeRange()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
Model enemyModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Location = new LocationPoint(0, 0, 0);
enemyModel.Location = new LocationPoint(1, 0, 0);
enemyModel.Initiative = 2;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel, testModel });
bool correctExceptionThrown = false;
try
{
testModel.Hide();
}
catch (HidingException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_StopHidingIfModelMovesIntoEnemyInitiativeRange()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
Model enemyModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Location = new LocationPoint(0, 0, 0);
enemyModel.Location = new LocationPoint(5, 0, 0);
enemyModel.Initiative = 3;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel, testModel });
testModel.IsHiding = true;
testModel.Movement = 4;
bool correctExceptionThrown = false;
try
{
testModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_StopHidingIfEnemyMovesWithinInitiativeRange()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
Model enemyModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0);
testModel.Location = new LocationPoint(5, 0, 0);
enemyModel.Location = new LocationPoint(0, 0, 0);
enemyModel.Initiative = 3;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel, testModel });
testModel.IsHiding = true;
enemyModel.Movement = 4;
bool correctExceptionThrown = false;
try
{
enemyModel.MoveModel(4, 0, 0);
}
catch (HidingException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}

The passing code sits in Hide, and is very pleasantly simple. Hide becomes very long:

public void Hide()
{
if (this.IsCharging || this.IsRunning)
{
HidingException ex = new HidingException("Cannot hide if charged or ran this turn.");
throw ex;
}
else
{
List<IModel> enemyModels = (from models in this._gameManager.Models
where models.Player != this.Player
&& (this._objectManager.GetLineOfSight(models, this) > 0.6 || this.GetDistanceFrom(models.Location.X, models.Location.Y, models.Location.Z) <= models.Initiative)
select models).ToList();
if (enemyModels.Count > 0)
{
this.IsHiding = false;
HidingException ex = new HidingException("Cannot hide in sight or Initiative range of enemy models.");
ex.EnemyModels = enemyModels;
throw ex;
}
else
{
this.IsHiding = true;
}
}
}

But this breaks several other tests. The reason is that some of the previously written tests are not setting locations for the enemy and test models, and the distance between them (zero) is always equal to or less than the default Initiative value (also zero). Changing the tests to give them a bit of space fixes this problem.

Since a model can remain hidden for several successive turns, it doesn’t need to reset the IsHiding flag – Hiding for all hidden models is checked whenever a model is moved, so it will be taken out of hiding when a player wishes or when it is chosen.

Next time, I move off of page 11, but I’m not sure where to yet…

Learning Unit Testing IX – Moving On With Movement

Having gotten those knotty problems with world management, distances, directions, etc all out of the way I can finally move off of page 10. So what’s next? Page 11! Hooray!

It begins with a description of a charge move. A charge works like a run, at double-move rate, with the model (hopefully) ending up in contact with an enemy, or with a low wall that is also in contact with the enemy. The movement side of things should be fairly easy, but describing ‘base contact’ might be slightly more difficult.

In the real game, a model’s base is usually a circle 25mm in diameter. This is close enough to one inch to be treated as one of the arbitrary units I’m using for movement and measurement, so a model can be described as having a width of 1. If I choose the location to describe the centre-point of a model’s base, then ‘base contact’ would be a point equal to half the charger’s width plus half the target’s width, to allow for future models that may be bigger or smaller than a normal one (I’m fondly thinking of Ogryns!)

I started skirting around the issue slightly in the last post, but I need to seriously consider scenery and models as things with width and height, more than points on a virtual graph. The scenery will also need a different method of determining base contact, since it won’t often have a circular base.

It’s taking me a while to work out exactly which direction (no pun intended) to go with this. A charge sounds exactly like a regular run move, except you can (and are expected to!) get within 8” of an enemy model without stopping, and it declares that the model “Can do nothing for the rest of the turn.” To that end, I believe that only three new tests are needed, on a ‘charge’ method – one that makes sure a charging model can approach within 8” of an enemy, one that checks that an ‘isCharging’ flag is set so that the model can’t take any more actions this turn, and one that checks that that flag is removed at the beginning of a turn.

The first test – that the model doesn’t stop moving close to an enemy – is almost identical to the one that checks that it does stop moving close to an enemy, but with a different expected final position and no exception thrown.

[TestMethod]
public void Charge_CanGetWithin8InchesOfEnemy()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Location.X = 9;
enemyModel.Location.Y = 0;
enemyModel.Location.Z = 0;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
bool exceptionThrown = false;
try
{
mockObjectManager.Setup(item => item.GetDistanceBetween(testModel.Location, It.IsAny<float>(), It.IsAny<float>(), It.IsAny<float>())).Returns(8);
mockObjectManager.Setup(item => item.GetPointOfIntersection(testModel.Location, It.IsAny<LocationPoint>(), enemyModel.Location, 8)).Returns(new LocationPoint(1, 0, 0));
mockObjectManager.Setup(item => item.GetLineOfSight(testModel, enemyModel)).Returns(1);
testModel.Charge(8, 0, 0);
}
catch (MovementException ex)
{
exceptionThrown = true;
}
Assert.IsFalse(exceptionThrown);
Assert.AreEqual(8, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}

The code to pass this test is extremely simple:

private bool _isCharging = false;
public void Charge(float positionX, float positionY, float positionZ)
{
_isCharging = true;
MoveModel(positionX, positionY, positionZ);
}

And a quick change to the ValidateMove method to check if the model is charging before validating the ‘within 8” of an enemy’ rule:

if (this.TotalDistanceMoved + distanceToPoint > this.Movement && _isCharging == false)

I almost feel silly for thinking it would be any harder than that, but then I was originally worrying about the base sizes, low wall restriction, etc, that is all part of ‘being in base contact’ – as far as Movement is concerned, it doesn’t matter if the models are in base contact after a charge, only that a charge was attempted. The next test, then, checks that the flag is set:

[TestMethod]
public void Charge_SetsIsChargingFlag()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
mockObjectManager.Setup(item => item.GetDistanceBetween(testModel.Location, It.IsAny<float>(), It.IsAny<float>(), It.IsAny<float>())).Returns(8);
testModel.Charge(8, 0, 0);
Assert.IsTrue(testModel.IsCharging);
}

The code only needed to change the private _isCharging to a public IsCharging property, and again – straight into the pass. To make sure the NewTurn method resets the charging flag:

[TestMethod]
public void NewTurn_SetsIsChargingToFalse()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Charge(8, 0, 0);
Assert.IsTrue(testModel.IsCharging);
testModel.NewTurn();
Assert.IsFalse(testModel.IsCharging);
}

Code to pass that test is just insultingly simple. At this time, I realised that the IsRunning flag might not have been set back to false at the beginning of a new turn – there’s no test for it, so I’ll add that in now.

[TestMethod]
public void NewTurn_SetsIsRunningToFalse()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.MoveModel(8, 0, 0);
Assert.IsTrue(testModel.IsRunning);
testModel.NewTurn();
Assert.IsFalse(testModel.IsRunning);
}

And as I predicted – it fails! The passing code should, again, be fairly easy to figure out. So I now have all the basics of charging, without the ‘is in base contact’ checks which will mostly be mathematical functions, and abstracted out without really implementing them (as I decided earlier).

The next challenge is Hiding, then we can move away from page 11 – much faster than getting away from page 10 was, at any rate! As I see it, the following things are necessary to check for hiding:

  • A model can choose to hide if it ends it’s turn in ‘reasonable’ cover
  • A model can move about and stay hidden, as long as it doesn’t leave the ‘reasonable’ cover.
  • A model cannot run or charge and hide in the same turn.
  • If an enemy model moves to a position it can see the hiding model, it ceases to be hidden.
  • A model ceases to be hidden if an enemy is within it’s Initiative value in inches of the model.

I will define ‘reasonable’ cover as being in at least 40% cover – this value could easily be tweaked later. I had decided while brainstorming before that line-of-sight shouldn’t return a boolean value, rather a percentage of the target that is visible. In systems that can determine only ‘visible’ and ‘not visible’, the IObjectManager would obviously return 0 and 1 and nothing in between. More subtle systems would give a range of values, meaning that cover saves (when I get to shooting) will take account of partial cover, heavy cover, etc. The first test then will check line of sight from every enemy, to make sure that it is not more than 60% visible to any of them.

[TestMethod]
public void Hide_SetsFlagIfInReasonableCover()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
bool exceptionThrown = false;
try
{
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(0.5f);
testModel.Hide();
}
catch (HidingException ex)
{
exceptionThrown = true;
}
Assert.IsFalse(exceptionThrown);
Assert.IsTrue(testModel.IsHiding); }

Code to pass this test:

public void Hide()
{
List<IModel> enemyModels = (from models in this._gameManager.Models
where models.Player != this.Player
&& this._objectManager.GetLineOfSight(models, this) > 0.6
select models).ToList();
if (enemyModels.Count > 0)
{
HidingException ex = new HidingException();
ex.EnemyModels = enemyModels;
throw ex;
}
else
{
this.IsHiding = true;
}
}

Using some lovely LINQy goodness! The next test:

[TestMethod]
public void Hide_ThrowsExceptionIfInSight()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
bool exceptionThrown = false;
try
{
mockObjectManager.Setup(item => item.GetLineOfSight(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(1);
testModel.Hide();
}
catch (HidingException ex)
{
exceptionThrown = true;
Assert.AreEqual(enemyModel, ex.EnemyModels[0]);
}
Assert.IsTrue(exceptionThrown);
}

Needs no modification. Maybe I went a bit too far with the code writing, since I knew what the test would be in advance? I played with the figures to make sure that the test fails when it should.

The following tests should prevent the Hiding flag staying set when running or charging:

[TestMethod]
public void Hide_RemoveHidingFlagWhenRunning()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.IsHiding = true;
testModel.Movement = 4;
testModel.MoveModel(6, 0, 0);
Assert.IsFalse(testModel.IsHiding);
}
[TestMethod]
public void Hide_RemoveHidingFlagWhenCharging()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.IsHiding = true;
testModel.Movement = 4;
testModel.Charge(6, 0, 0);
Assert.IsFalse(testModel.IsHiding);
}

The code for both these tests is simple – change MoveModel and Charge to look like:

public void MoveModel(float positionX, float positionY, float positionZ)
{
try
{
ValidateMove(ref positionX, ref positionY, ref positionZ);
}
catch (Exception ex)
{
throw;
}
finally
{
this.TotalDistanceMoved += GetDistanceFrom(positionX, positionY, positionZ);
this.Location.X = positionX;
this.Location.Y = positionY;
this.Location.Z = positionZ;
if (this.TotalDistanceMoved > this.Movement)
{
this.IsHiding = false;
this.IsRunning = true;
}
}
}
public void Charge(float positionX, float positionY, float positionZ)
{
IsHiding = false;
IsCharging = true;
MoveModel(positionX, positionY, positionZ);
}

The next two tests check that a model can’t hide if it charged or ran this turn:

[TestMethod]
public void Hide_CannotHideIfRanInSameTurn()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.IsRunning = true;
bool correctExceptionThrown = false;
try
{
testModel.Hide();
}
catch (HidingException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void Hide_CannotHideIfChargedInSameTurn()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.IsCharging = true;
bool correctExceptionThrown = false;
try
{
testModel.Hide();
}
catch (HidingException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(testModel.IsHiding);
Assert.IsTrue(correctExceptionThrown);
}

I’m really liking how simple these tests are to pass at the moment. The code to solve both of those tests goes at the top of the Hide method, and is just a quick check:

if (this.IsCharging || this.IsRunning)
{
HidingException ex = new HidingException("Cannot hide if charged or ran this turn.");
throw ex;
}

This is getting a bit too long (I’m thinking that 2000 words, including code, should be a maximum limit on these). I’ll continue more Hiding tests next time!

Learning Unit Testing VIII – Emergent Design

I’ve been bracing myself for this moment since I started the project – I’m coming to believe that I’ve been heading in a slightly wrong direction.

Part of TDD is the idea of ‘emergent design’, and letting the correct design emerge from the process of writing minimal code to pass tests. At the same time, it’s necessary to keep an eye on the bigger picture so you know where exactly the code should be written.

I’ve been making the MoveModel method into a huge monster, that checks all sorts of things, and as I started investigating how to implement line of sight, I realised that this functionality should probably be moved out into some sort of utility class. MoveModel itself is now checking distances between two points (this will be duplicated later), proximity detection (probably to be used later), and as I said – was about to take on line of sight. All these details could be worked out mathematically as I was doing them, but some frameworks (such as XNA) might provide better ways to calculate them, with things such as Rays, or… something else XNAish.

The best way to handle that would be to refactor all the ‘utility’ calculations into a separate class, exposed via an interface so that it can be swapped out. That way, I can abstract out and mock all the calculations in MoveModel to deal more with the situation parameters and appropriate responses and less about the calculations themselves. At the same time, I can write tests for this new utility class so that I can test the purely mathematical aspects of the same calculations. The interactions become easier (true, false, and integer responses rather than the broad spectrum of mathematical output) since I don’t have to test multiple configurations and situation-specific responses, just the two separate things that should be behaving.

The other benefit will be that if I eventually do take this library over to an XNA frontend, I can swap out that mathematical calculator for something more efficient for the framework to use. But the basic mathematical way will still be available if I choose instead to port it to Silverlight.

I also took this opportunity to move the X, Y and Z co-ordinates into a separate class – LocationPoint.

Here’s the set of tests for MoveModel:

[TestClass]
public class ModelTests
{
[TestMethod]
public void NewTurn_SetsDistanceMovedToZero()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.TotalDistanceMoved = 4;
testModel.NewTurn();
Assert.AreEqual(0, testModel.TotalDistanceMoved);
}
[TestMethod]
public void MoveModel_PositionHasChanged()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
float newX = 2;
float newY = 2;
float newZ = 2;
testModel.MoveModel(newX, newY, newZ);
Assert.AreEqual(2, testModel.Location.X);
Assert.AreEqual(2, testModel.Location.Y);
Assert.AreEqual(2, testModel.Location.Z);
Assert.IsFalse(testModel.IsRunning);
}
[TestMethod]
public void MoveModel_TotalMovementInASingleTurnCannotExceedModelMovement_ThrowsMovementException()
{
bool correctExceptionThrown = false;
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
testModel.MoveModel(newX, newY, newZ);
try
{
testModel.MoveModel(0, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_MakeTwoSmallMovementsWithoutGoingOverMovementRate()
{
bool correctExceptionThrown = false;
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
float newX = 1;
float newY = 1;
float newZ = 0;
try
{
testModel.MoveModel(newX, newY, newZ);
testModel.MoveModel(0, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_CannotMoveFurtherThanModelsMovement()
{
bool correctExceptionThrown = false;
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
float newX = 5;
float newY = 6;
float newZ = 3;
try
{
testModel.MoveModel(newX, newY, newZ);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}
[TestMethod]
public void MoveModel_MovesDistanceOverMovementRateAndSetsIsRunningFlag()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>());
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
testModel.MoveModel(newX, newY, newZ);
Assert.IsTrue(testModel.IsRunning);
}
[TestMethod]
public void MoveModel_StopsRunningWithin8InchesOfEnemyModel()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Location.X = 9;
enemyModel.Location.Y = 0;
enemyModel.Location.Z = 0;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
bool correctExceptionThrown = false;
try
{
mockObjectManager.Setup(item => item.GetDistanceBetween(testModel.Location, It.IsAny<float>(), It.IsAny<float>(), It.IsAny<float>())).Returns(8);
mockObjectManager.Setup(item => item.GetPointOfIntersection(testModel.Location, It.IsAny<LocationPoint>(), enemyModel.Location, 8)).Returns(new LocationPoint(1, 0, 0));
mockObjectManager.Setup(item => item.GetLineOfSight(testModel, enemyModel)).Returns(1);
testModel.MoveModel(8, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
Assert.AreEqual(1, ex.FinalPosition.X);
Assert.AreEqual(0, ex.FinalPosition.Y);
Assert.AreEqual(0, ex.FinalPosition.Z);
}
Assert.IsTrue(correctExceptionThrown);
Assert.AreEqual(1, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
[TestMethod]
public void MoveModel_ContinuesRunningWithEnemyMoreThan8InchesAway()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Location.X = 18;
enemyModel.Location.Y = 0;
enemyModel.Location.Z = 0;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
bool correctExceptionThrown = false;
try
{
testModel.MoveModel(8, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
Assert.AreEqual(8, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
[TestMethod]
public void MoveModel_ContinuesMovingWithin8InchesOfEnemyModelNotRunning()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Location.X = 9;
enemyModel.Location.Y = 0;
enemyModel.Location.Z = 0;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
bool correctExceptionThrown = false;
try
{
testModel.MoveModel(3, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
Assert.AreEqual(3, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
[TestMethod]
public void MoveModel_ContinuesRunningWithin8InchesOfFriendlyModel()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model friendlyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
friendlyModel.Location.X = 9;
friendlyModel.Location.Y = 0;
friendlyModel.Location.Z = 0;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { friendlyModel });
Model testModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
bool correctExceptionThrown = false;
try
{
testModel.MoveModel(8, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
Assert.AreEqual(8, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
[TestMethod]
public void MoveModel_ContinuesRunningWithin8InchesOfEnemyModelIfOutOfSight()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
Mock<IObjectManager> mockObjectManager = new Mock<IObjectManager>();
Model enemyModel = new Model(1, mockGameManager.Object, mockObjectManager.Object);
enemyModel.Location.X = 12;
enemyModel.Location.Y = 0;
enemyModel.Location.Z = 0;
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
Mock<IScenery> mockScenery = new Mock<IScenery>();
mockScenery.Setup(item => item.IsBlocking(It.IsAny<IModel>(), It.IsAny<IModel>())).Returns(true);
mockGameManager.Setup(item => item.Models).Returns(new List<IModel>() { enemyModel });
mockGameManager.Setup(item => item.SceneryObjects).Returns(new List<IScenery>() { mockScenery.Object });
Model testModel = new Model(2, mockGameManager.Object, mockObjectManager.Object);
testModel.Movement = 4;
testModel.Location.X = 0;
testModel.Location.Y = 0;
testModel.Location.Z = 0;
bool correctExceptionThrown = false;
try
{
testModel.MoveModel(8, 0, 0);
}
catch (MovementException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
Assert.AreEqual(8, testModel.Location.X);
Assert.AreEqual(0, testModel.Location.Y);
Assert.AreEqual(0, testModel.Location.Z);
}
}

And the code needed in MoveModel itself:

public void MoveModel(float positionX, float positionY, float positionZ)
{
try
{
ValidateMove(ref positionX, ref positionY, ref positionZ);
}
catch (Exception ex)
{
throw;
}
finally
{
this.TotalDistanceMoved += GetDistanceFrom(positionX, positionY, positionZ);
this.Location.X = positionX;
this.Location.Y = positionY;
this.Location.Z = positionZ;
if (this.TotalDistanceMoved > this.Movement)
{
this.IsRunning = true;
}
}
}
private bool ValidateMove(ref float positionX, ref float positionY, ref float positionZ)
{
double distanceToPoint = GetDistanceFrom(positionX, positionY, positionZ);
if (distanceToPoint + this.TotalDistanceMoved > this.Movement * 2)
{
MovementException ex = new MovementException("The model cannot move further than it's Movement rate.");
ex.FinalPosition.X = this.Location.X;
ex.FinalPosition.Y = this.Location.Y;
ex.FinalPosition.Z = this.Location.Z;
throw ex;
}
if (this.TotalDistanceMoved + distanceToPoint > this.Movement)
{
foreach (IModel enemyModel in _gameManager.Models.Where(item => item.Player != this.Player))
{
LocationPoint intersectionPoint = _objectManager.GetPointOfIntersection(this.Location, new LocationPoint(positionX, positionY, positionZ), enemyModel.Location, 8);
if (intersectionPoint != null)
{
if (_objectManager.GetLineOfSight(this, enemyModel) > 0)
{
MovementException ex = new MovementException("The model cannot run within 8\" of an enemy model.");
ex.FinalPosition = intersectionPoint;
positionX = intersectionPoint.X;
positionY = intersectionPoint.Y;
positionZ = intersectionPoint.Z;
throw ex;
}
}
}
}
return true;
}

The lesson I’ve learned from this as far as emergent design goes is that I should be more willing to abstract something out if it doesn’t look like it belongs, and be prepared to program against interfaces that are not implemented yet. I believe that’s the way TDD works, and will keep code exactly where it needs to be and not spread around (as it was starting to do in the Model class…)

Learning Unit Testing VII – Running and Redesigning

I can see this page 10 being a pretty big thorn in my side. After stating fairly clearly

models can move up to their move rate

under the ‘Moving’ heading, it then says

A running fighter can move at double speed: 8″ rather than 4″, for example.

This is going to have a fairly large impact on the tests involved. The test for exceeding total movement (both in one and two moves) will only throw an exception if the total movement is double the model’s Movement rate now. In addition, a new test is going to have to be added to ensure that moving over the Movement rate sets an ‘IsRunning’ flag to true. The first test to add would be one that allows a model to move more than it’s Movement rate, but sets the IsRunning flag.

[TestMethod]
public void MoveModel_MovesDistanceOverMovementRateAndSetsIsRunningFlag()
{
Model testModel = new Model();
testModel.Movement = 4;
testModel.PositionX = 0;
testModel.PositionY = 0;
testModel.PositionZ = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
testModel.MoveModel(newX, newY, newZ);
Assert.IsTrue(testModel.IsRunning);
}

Even with the correct code added to MoveModel –

if (this.TotalDistanceMoved > this.Movement)
{
this.IsRunning = true;
}

– this doesn’t work. Because ValidateMove is throwing an exception (as we designed in earlier). So ValidateMove has to allow up to twice the MovementRate.

if (GetDistanceFrom(positionX, positionY, positionZ) + this.TotalDistanceMoved > this.Movement * 2)
{
throw new ArgumentException("The model cannot move further than it's Movement rate.");
}

Now, all the tests pass. But should they be? Checking my working, the MoveModel_CannotMoveFurtherThanModelsMovement test is moving the model more than twice it’s movement rate. So it is correct to throw an exception, and therefore pass the test. Anything testing to stay beneath the model’s Movement rate is obviously going to be beneath it’s doubled Movement rate, so they will continue to pass. MoveModel_TotalMovementInASingleTurn… that tests whether two movements that combined make more than the model’s Movement is also working correctly. But I’ve noticed I had an error in that test:

[TestMethod]
public void MoveModel_TotalMovementInASingleTurnCannotExceedModelMovement_ThrowsArgumentException()
{
bool correctExceptionThrown = false;
try
{
Model testModel = new Model();
testModel.Movement = 4;
testModel.PositionX = 0;
testModel.PositionY = 0;
testModel.PositionZ = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
testModel.MoveModel(newX, newY, newZ);
testModel.MoveModel(0, 0, 0);
}
catch (ArgumentException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}

The try catch was working on both MoveModel calls – and those values (after the calculation fix a couple of posts back) would have sent it way over the Movement on the first move, not the second. Moving the try…catch so that it only covers the second call to MoveModel would check that it’s failing in precisely the right place. By reversing my change to ValidateMove, I can confirm that the test would have failed before if the try…catch was in the right place. Putting it back in, and it throws an exception on the second MoveModel call, just as it should do. All the tests pass, and I’m a little more comfortable about them testing the right thing.

Lesson learned: Only test exactly the line you expect to break things.

The rest of the ‘Running’ section mentions not being able to shoot – this is something that we can cover in the Shooting section (after Movement), and isn’t important right now. The next bit that should matter is that a running model must stop 8 units away from an enemy model that he can see. This brings a number of things into play immediately:

  1. Can we identify models as being enemies?
  2. Can we identify which models can see others?
  3. How do we perform the sight checks all along it’s movement to work out if it can see the enemy (they may not be in sight at the beginning of the move, only halfway along it)?
  4. What action should be taken if the model’s movement is disrupted – should it still count as running? How can it be prevented from moving further?

Before we write tests, we should explore the problem a bit further.

Essentially, the enemy is at the centre of a circle 16 units in diameter (radius of 8). The active model’s path may intersect the circle at some point. If it does so, the active model must stop at the point along that path nearest to his starting point, providing that he can draw a line of sight to the enemy model at the centre of the circle.

So we only really need to work out if any point of the active model’s path intersects the enemy ‘disruption’ circles, and check those sections of the path for line-of-sight to the centre of the circle.

Learning Unit Testing VI – Code Coverage

Before I get into finally finishing off that blasted page 10, I thought I’d go through a code coverage run to see how I was doing. Test Driven Development, see, everything should be covered by a test.

image

7.41% of the code is not covered by tests! I must be doing this wrong…

image_3

Phew, that was a relief. Turns out all of that uncovered code is in the WrongPhaseException I created from the Exception snippit without thinking about it. Activating the code coverage colouring in the code shows me what I’ve missed:

image_4

Because I only use one constructor out of a possible four, it counts as only 25% coverage and brings my average code coverage way down.

If I was to remove all those extra constructors (and leave them to the default Exception behaviour), would it improve my code coverage score?

image_5

Absolutely. It might be a bit like cheating (those methods are still not covered by tests), but since they’re core parts of the framework it doesn’t feel necessary to me. Short and sweet, on to part VII!

This is a learning project I am documenting in order to teach myself TDD and unit testing – as I publish this, I have already written many parts in advance but I want to know what I’m doing wrong! If you see improvements or corrections, either to the code or the process, please leave a comment and let me know! Introduction to this project can be found here.

Learning Unit Testing V – Refactoring Mistakes

I implied in the last post that I had made a mistake when I moved the MoveModel code into the Model class from the MovementManager class. Now, tests on the MovementManager class are actually testing functionality in the Model class. I also discovered that I am not properly testing the ‘GetDistanceFrom’ method with known data at all. This will have to be corrected.

Since I already have Model implementing an IModel interface, it’ll be easy enough to mock the Model part in the MovementManager. To see what tests are reaching into Model.MoveModel, I can just alter that method to throw an exception and see which tests fail.

image

Since this functionality has been moved to the Model class, these tests should actually be made against the Model class itself. Simple? Yes, but incorrect, as I found out next – it still fails on one condition and checking further, I don’t believe it’s checking the other one correctly. The validation of whether or not a model may make a move (ie, the distance is still under it’s Movement rate) is done in the MovementManager. My two options at this point are: move the validation back to the MovementManager and do a lot of mocking around the Model, or to move that validation into the Model as a ValidateMove method. Then the movement manager really becomes a mere progress tracker for the Movement phase… but I think that’s probably the best idea in this situation. Just one more test to move over (MoveModel_CannotMoveFurtherThanModelsMovement), then the GetDistanceFrom method needs to be tested. I think the following cases will need to be covered:

  • Full three-dimensional movement (all values change)
  • Two-dimensional movement (one value doesn’t change)
  • One-dimensional movement (only one value does change)
  • Cover reverse cases of each of the above (so the direction is backwards)

This test (worked on with a calculator and doodles to prove my working), however:

[TestMethod]
public void GetDistanceFrom_CorrectAnswerWhenAllValuesChange()
{
Model testModel = new Model();
testModel.PositionX = 1;
testModel.PositionY = 3;
testModel.PositionZ = 4;
double result = testModel.GetDistanceFrom(2, 5, 7);
Assert.AreEqual(3.74, Math.Round(result, 2));
}

Shows me that either my test calculation is wrong or my original working is wrong. In the GetDistanceFrom method, this line

distance = Math.Sqrt(Math.Sqrt((differenceX * differenceX) + (differenceY * differenceY)) + (differenceZ * differenceZ));

Should actually be

distance = Math.Sqrt((differenceX * differenceX) + (differenceY * differenceY) + (differenceZ * differenceZ));

So I got the calculation wrong. Now the test passes, but another one breaks.

image_3

This is because that movement calculation now requires the model to move further than it’s allowed distance. Fixing that test’s values (from 3, 4, 5 to 2, 2, 2) and adding in the following tests:

[TestMethod]
public void GetDistanceFrom_CorrectAnswerWhenTwoValuesChange()
{
Model testModel = new Model();
testModel.PositionX = 1;
testModel.PositionY = 3;
testModel.PositionZ = 4;
double result = testModel.GetDistanceFrom(1, 5, 7);
Assert.AreEqual(3.61, Math.Round(result, 2));
}
[TestMethod]
public void GetDistanceFrom_CorrectAnswerWhenOneValueChanges()
{
Model testModel = new Model();
testModel.PositionX = 1;
testModel.PositionY = 3;
testModel.PositionZ = 4;
double result = testModel.GetDistanceFrom(1, 3, 7);
Assert.AreEqual(3, Math.Round(result, 2));
}
[TestMethod]
public void GetDistanceFrom_CorrectAnswerWhenAllValuesChange_Reversed()
{
Model testModel = new Model();
testModel.PositionX = 2;
testModel.PositionY = 5;
testModel.PositionZ = 7;
double result = testModel.GetDistanceFrom(1, 3, 4);
Assert.AreEqual(3.74, Math.Round(result, 2));
}
[TestMethod]
public void GetDistanceFrom_CorrectAnswerWhenTwoValuesChange_Reversed()
{
Model testModel = new Model();
testModel.PositionX = 1;
testModel.PositionY = 5;
testModel.PositionZ = 7;
double result = testModel.GetDistanceFrom(1, 3, 4);
Assert.AreEqual(3.61, Math.Round(result, 2));
}
[TestMethod]
public void GetDistanceFrom_CorrectAnswerWhenOneValueChanges_Reversed()
{
Model testModel = new Model();
testModel.PositionX = 1;
testModel.PositionY = 3;
testModel.PositionZ = 7;
double result = testModel.GetDistanceFrom(1, 3, 4);
Assert.AreEqual(3, Math.Round(result, 2));
}

Gives me 19 passes out of 19 tests. Excellent – I was expecting some of the 0-distance values being squared and rooted to throw some sort of calculation exception in .Net, but I can get along with this result.

This leaves me with my refactoring mess cleaned up, a calculation error solved, a method I somehow missed tests for covered properly, and all ready to tackle the last part of page 10 – a much longer page than I had initially expected. “Ooh look”, said the me at the beginning of the week, “half the page is picture and prose, and the other half is mostly descriptive. One or two posts to talk about design ideas and I’ll be on page 20 in a fortnight!” Isn’t hindsight awesome.

This is a learning project I am documenting in order to teach myself TDD and unit testing – as I publish this, I have already written many parts in advance but I want to know what I’m doing wrong! If you see improvements or corrections, either to the code or the process, please leave a comment and let me know! Introduction to this project can be found here.

Learning Unit Testing IV – Diagrams and Mathematics

Programming uses maths. It’s hard to ignore sometimes, especially when modelling something that is inherently graphical and position based such as a game.

I’ve decided to give the models three different position indicators – PositionX, PositionY and PositionZ. This will describe their exact position anywhere on the board – including on a ladder, a walkway or the ground. When they are given instructions to move, they should change those position variables accordingly – but not past the maximum move distance.

My two options are to either pass in a distance and direction (both X direction and Y direction, like an aircraft would need to navigate) or a new set of position points – in essence, saying precisely ‘go here’. In the first, the new position would need to be calculated, and in the second the distance would need to be calculated. Either way, I’d need to write both sets of code at some point.

I think that it will be easier to back-track the distance and directions from a set of positions than it would be the other way around. Transforming a simple co-ordinate of (X, Y, Z) – where X is East-West, Y is North-South and Z is Up-Down – such as (1, 2, 1) to a new position of (2, 1, 2) tells me that the model has moved up a slope to the North-East for a distance of… actually, that bit gets harder. But it’s at least easy to work out with trigonometry!

image

On a two-dimensional plane, it is fairly simple to work out – as on the (hastily scribbled, not-to-scale) diagram to the left. This brings back distant memories of Pythagoras – a2 + b2 = c2 – or, the square root of the difference in X + the difference in Y is the distance between the two points.

On a three-dimensional world, this is a bit more complicated – it has to be reduced to two different two-dimensional triangles to calculate the distance. The difference between two points is the ‘difference in X’ value of the second triangle. Another way of putting it would be √ (√ ((X1 – X2)2 + (Y1 – Y2)2) + (Z1 – Z2)2). Excellent.

In my original example then, the answer would be 1.554 arbitrary units. I could work out the directions (angles) by using more of my dusty trigonometric knowledge, but I only need to know the distance at this point. All I need to know are some test values that give a value above a model’s Movement rate and some under, so I can test the outcomes of the MoveModel method. In the case of overshooting a model’s permitted movement, I think I’ll throw a new exception. I can see (a few pages down the line) that the model is going to need to be able to tell it’s distance from other objects anyway, so this could be checked before attempting to move the model.

Time for a test! These values will move the model just a smidgen over 4 units.

[TestMethod]
public void MoveModel_CannotMoveFurtherThanModelsMovement()
{
bool correctExceptionThrown = false;
try
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
MovementManager movementManager = new MovementManager(mockGameManager.Object);
Model testModel = new Model();
testModel.Movement = 4;
decimal newX = 5;
decimal newY = 6;
decimal newZ = 3;
movementManager.MoveModel(testModel, newX, newY, newZ);
}
catch (ArgumentException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}

The code to pass this in the MovementManager is:

public void MoveModel(Model testModel, float newX, float newY, float newZ)
{
if (testModel.Movement < testModel.GetDistanceFrom(newX, newY, newZ))
{
throw new ArgumentException("The model cannot move further than it's Movement rate.");
}
}

And in the Model itself is:

public float PositionX { get; set; }
public float PositionY { get; set; }
public float PositionZ { get; set; }
public double GetDistanceFrom(float positionX, float positionY, float positionZ)
{
double distance = 0;
double differenceX = this.PositionX - positionX;
double differenceY = this.PositionY - positionY;
double differenceZ = this.PositionZ - positionZ;
distance = Math.Sqrt(Math.Sqrt((differenceX * differenceX) + (differenceY * differenceY)) + (differenceZ * differenceZ));
return distance;
}

I can’t help but think that I am still over-designing this, but it feels about right to me. Finally, the test to ensure that the model actually does move:

[TestMethod]
public void MoveModel_PositionHasChanged()
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
MovementManager movementManager = new MovementManager(mockGameManager.Object);
Model testModel = new Model();
testModel.Movement = 4;
testModel.PositionX = 0;
testModel.PositionY = 0;
testModel.PositionZ = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
movementManager.MoveModel(testModel, newX, newY, newZ);
Assert.AreEqual(5, testModel.PositionX);
Assert.AreEqual(4, testModel.PositionY);
Assert.AreEqual(3, testModel.PositionZ);
}

Which requires a change to MoveModel –

public void MoveModel(Model testModel, float newX, float newY, float newZ)
{
if (testModel.Movement < testModel.GetDistanceFrom(newX, newY, newZ))
{
throw new ArgumentException("The model cannot move further than it's Movement rate.");
}
else
{
testModel.PositionX = newX;
testModel.PositionY = newY;
testModel.PositionZ = newZ;
}
}

Next, the situation that a model may move in two goes – and cannot go above it’s movement rate in both chunks. For this, we’ll have to record the distance moved.

[TestMethod]
public void MoveModel_TotalMovementInASingleTurnCannotExceedModelMovement_ThrowsArgumentException()
{
bool correctExceptionThrown = false;
try
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
MovementManager movementManager = new MovementManager(mockGameManager.Object);
Model testModel = new Model();
testModel.Movement = 4;
testModel.PositionX = 0;
testModel.PositionY = 0;
testModel.PositionZ = 0;
float newX = 5;
float newY = 4;
float newZ = 3;
movementManager.MoveModel(testModel, newX, newY, newZ);
movementManager.MoveModel(testModel, 0, 0, 0);
}
catch (ArgumentException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}

Movement of a model is starting to take on several different, inter-related actions so I’m going to move that stuff into the Model class itself. MoveModel in the MovementManager becomes simpler:

public void MoveModel(Model testModel, float newX, float newY, float newZ)
{
if (testModel.Movement < testModel.GetDistanceFrom(newX, newY, newZ) + testModel.TotalDistanceMoved)
{
throw new ArgumentException("The model cannot move further than it's Movement rate.");
}
else
{
testModel.MoveModel(newX, newY, newZ);
}
}

And the addition to the Model class is:

public double TotalDistanceMoved { get; private set; }
public void MoveModel(float positionX, float positionY, float positionZ)
{
this.TotalDistanceMoved += GetDistanceFrom(positionX, positionY, positionZ);
this.PositionX = positionX;
this.PositionY = positionY;
this.PositionZ = positionZ;
}

Next is to make sure we can make multiple moves without going over the limit and throwing an exception:

[TestMethod]
public void MoveModel_MakeTwoSmallMovementsWithoutGoingOverMovementRate()
{
bool correctExceptionThrown = false;
try
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
MovementManager movementManager = new MovementManager(mockGameManager.Object);
Model testModel = new Model();
testModel.Movement = 4;
testModel.PositionX = 0;
testModel.PositionY = 0;
testModel.PositionZ = 0;
float newX = 1;
float newY = 1;
float newZ = 0;
movementManager.MoveModel(testModel, newX, newY, newZ);
movementManager.MoveModel(testModel, 0, 0, 0);
}
catch (ArgumentException ex)
{
correctExceptionThrown = true;
}
Assert.IsFalse(correctExceptionThrown);
}

This doesn’t need any new code, it just proves that the existing code still works. Finally, there needs to be a check that the TotalDistanceMoved will reset to 0 at the beginning of each turn. What is going to be the trigger for that? Currently, the only place that a new turn is known about is the GameManager, and the IncrementPhase method. The question is, therefore, how should the model be informed of this change? Should the GameManager contain a collection of all the models currently in the game, so the IncrementPhase method can just iterate that collection? Or should models be held elsewhere, and subscribe to a ‘new turn’ event that is triggered? I think it makes most sense for the models to be stored in the GameManager – at least at this time – and for them to expose a ‘new turn’ method.

I have become aware as I am refactoring this code around that I’m starting to break the isolation of the unit tests – some tests are calling methods in classes that are not the class under test. This will obviously need to be refactored, but I’d like to finish the current train of thought first. To do that properly though, I need to add a new test class – ModelTests – and test the NewTurn method on that as well as making sure the GameManager calls NewTurn on each of it’s (mocked) Models. Testing these two things becomes an integration test, and cannot guarantee which class/method is actually responsible for failing a test.

[TestMethod]
public void IncrementTurnPhase_CallsNewTurnOnModels()
{
GameManager manager = new GameManager(2);
Mock<IModel> mockModel = new Mock<IModel>();
manager.Models.Add(mockModel.Object);
manager.IncrementPhase(); // Go to Shooting
manager.IncrementPhase(); // Go to Close Combat
manager.IncrementPhase(); // Go to Recovery
manager.IncrementPhase(); // Go back to Movement
mockModel.Verify(item => item.NewTurn(), Times.Once());
}

In the above test, I am adding a mock model into the GameManager, then verifying that the NewTurn method is called once. I add the Models property, create an IModel interface, change the IncrementPhase code to

public void IncrementPhase()
{
if (CurrentPhase == TurnPhase.Recovery)
{
CurrentPhase = TurnPhase.Movement;
if (CurrentPlayersTurn == NumberOfPlayers)
{
CurrentPlayersTurn = 1;
}
else
{
CurrentPlayersTurn++;
}
foreach (IModel model in this.Models)
{
model.NewTurn();
}
}
else
{
CurrentPhase++;
}
}

And… four tests fail on me!

image_3

This is exactly the purpose of unit testing. In this case, I realised that the Models collection (List<IModel>) hadn’t been initialised – these tests suddenly failing are a result of the turn ending and attempting to iterate through a collection that hasn’t been set up yet – a NullReferenceException. One quick tweak to the constructor, and they’re all passing again.

Finally, the test on the Model side to make sure that it sets TotalDistanceMoved back to zero.

[TestClass]
public class ModelTests
{
[TestMethod]
public void NewTurn_SetsDistanceMovedToZero()
{
Model testModel = new Model();
testModel.TotalDistanceMoved = 4;
testModel.NewTurn();
Assert.AreEqual(0, testModel.TotalDistanceMoved);
}
}

And the code in Model being insultingly simple:

public void NewTurn()
{
this.TotalDistanceMoved = 0;
}

Thirteen tests so far, and all passing. A quick lesson in how unit tests can quickly point to something going awry – though I am sure more important lessons will come along as I move through the project – and we’re almost to the end of page 10 in the rulebook. This has been a long section, and it’s a good point to leave it at.

This is a learning project I am documenting in order to teach myself TDD and unit testing – as I publish this, I have already written many parts in advance but I want to know what I’m doing wrong! If you see improvements or corrections, either to the code or the process, please leave a comment and let me know! Introduction to this project can be found here.

Learning Unit Testing III – Emergent Design

So having seen the answers on my own StackOverflow question on TDD Design, I’ve decided to go ahead with the ‘emergent design’ crowd and see what happens. It appears that the refactoring and mid-stream redesigning is an integral part of the TDD process. It’ll all, of course, be described here (the process might be a little bit stream-of-consciousness) as I go through my design decisions ‘out loud’).

I got up to the end of page 9 last time, about to start on the Movement section. In keeping with the single responsibility principle, I’ll be putting movement logic in it’s own class. Movement happens in a strict order – all chargers are declared and moved first, then any compulsory moves are made, and finally ‘everything else’ movement. This would be represented in the MovementManager class as having an internal phase structure, just like the GameManager.

The question is, where should this be called from? The decision to stop moving chargers and move onto the next sub-phase is a player choice, therefore a user interface trigger (just as IncrementPhase). It is not something that can be automated or inferred. Should the IncrementPhase method (and associated fields) be moved out into a TurnManager class which is owned by GameManager – GameManager can then expose valid actions based on what phase and whose turn it is. Would that even need to be moved out into another class – if I am deciding actions based on what the GameManager will expose to a user interface, the turn logic can remain in GameManager.

Alternatively, the MovementManager can broadly restrict actions based on the properties of it’s parent (GameManager) and regulate it’s own, internal restrictions based on sub-phase and a variation of that logic. This seems like the simplest solution – I can raise a custom exception if a method is called on the MovementManager that is invalid (wrong sub-phase, or wrong phase entirely). Time for a new cl- uh, test class, obviously. Some of the tests can be cribbed from the existing GameManagerTests, but there’s no real worry about whose turn it is.

[TestClass]
public class MovementManagerTests
{
[TestMethod]
public void IncrementSubPhase_RollsBackToMoveChargersAfterEverythingElse()
{
MovementManager movementManager = new MovementManager();
movementManager.IncrementSubPhase(); // Go to compulsory moves
movementManager.IncrementSubPhase(); // Go to everything else
movementManager.IncrementSubPhase(); // Go back to Move Chargers
Assert.AreEqual(MovementSubPhase.MoveChargers, movementManager.CurrentSubPhase);
}
[TestMethod]
public void IncrementSubPhase_MovesForwardSubPhase()
{
MovementManager movementManager = new MovementManager();
MovementSubPhase firstPhase = movementManager.CurrentSubPhase;
movementManager.IncrementSubPhase();
MovementSubPhase secondPhase = movementManager.CurrentSubPhase;
Assert.AreNotEqual(firstPhase, secondPhase);
}
}

As expected, after resolving build errors (new class, property and method stubs) the two tests fail. And with the barest minimum of code:

public class MovementManager
{
public MovementSubPhase CurrentSubPhase { get; private set; }
public void IncrementSubPhase()
{
if (CurrentSubPhase == MovementSubPhase.EverythingElse)
{
CurrentSubPhase = MovementSubPhase.MoveChargers;
}
else
{
CurrentSubPhase++;
}
}
}

It all passes. Next up, there needs to be a way of preventing the Movement sub-phase from advancing when it is not the Movement phase at all. The expected behaviour for this should be a custom exception to be thrown, and dealt with elsewhere (presumably, the hypothetical UI). To fool it that it is not the Movement phase, it needs to have a reference to the GameManager – passed in as a constructor parameter to the MovementManager. The GameManager passed in for this test also needs to be mocked, so it will need to be given a public interface. One quick mocking later:

[TestMethod]
public void IncrementSubPhase_ThrowsExceptionIfNotMovementPhase()
{
bool correctExceptionThrown = false;
try
{
Mock<IGameManager> mockGameManager = new Mock<IGameManager>();
mockGameManager.Setup(item => item.CurrentPhase).Returns(TurnPhase.Shooting);
MovementManager movementManager = new MovementManager(mockGameManager.Object);
movementManager.IncrementSubPhase();
}
catch (WrongPhaseException ex)
{
correctExceptionThrown = true;
}
Assert.IsTrue(correctExceptionThrown);
}

Which is passed by:

private void ValidatePhase()
{
if (_gameManager.CurrentPhase != TurnPhase.Movement)
{
throw new WrongPhaseException("Cannot perform Movement actions in the " + _gameManager.CurrentPhase.ToString() + " phase.");
}
}

And calling that at the beginning of the IncrementSubPhase method.

Now it’s time to make some changes to the (as yet unused) Model class. Now it starts to get into things in a bit more of a distinct rule structure – the next line states that ‘models can move up to their move rate in inches in any direction’. I’m deciding that ‘inches’ refers to an arbitrary (and divisible) number, so a decimal will serve very well to decide how far they’ve moved, and how far they can move. The movement manager can either take in a co-ordinate to specify the target position, and change the model’s position to as far as it can go towards that point, or take in a distance and direction and move the model as specified.

It’ll probably be easier to sort out the distance and direction at the moment – those calculations will need to be made in the code that calls the MovementManager – though at some future point, these could be made into helper methods on the MovementManager. To work out where the model’s new position is, we need diagrams – this will have to wait until the next post!

This has been a nice long post that gets about halfway through page 10. I’m still not sure I’m allowing the design to emerge – I am, for example, thinking a few tests ahead of where I am – but at this stage, I’ve not (consciously) made a decision about design based on that forewarning, only on the basis of ‘I want Movement code separate from Game Manager code). This is more to do with the single responsibility principle than some master design drawn upfront.

This is a learning project I am documenting in order to teach myself TDD and unit testing – as I publish this, I have already written many parts in advance but I want to know what I’m doing wrong! If you see improvements or corrections, either to the code or the process, please leave a comment and let me know! Introduction to this project can be found here.

Learning Unit Testing II – TDD vs. Traditional Design

As you can see, I’ve only done a small amount of Test-Driven Development so far. I notice that for small classes – so far, there’s only a very simple turn tracker – it’s easy to write a test first when there’s only very little code and no interaction with other classes. When things get a little more complex, will it still be so easy?

I am wondering how people use TDD when they have big projects (as this will invariably become). Is it possible to stick to just writing code to pass tests all the way through to the end? Surely, at some point, major refactoring will need to be done when something comes right out of nowhere. I can see one example I’ll run into at some point: in Necromunda, a model may go on ‘Overwatch’. This allows them to fire in their opponents turn, shooting at a target before it’s move, after it’s move or during it’s move. That’s probably going to require a major change to the Movement code (to account for the opponent taking an action), which could itself lead to a lot of refactoring. Should I try and design around this idea now, or wait until much, much later when I get to the Overwatch section to worry about it?

Whether or not there is a simple solution to that particular problem will depend on many things – not least, the movement and shooting code that is written before it. I would traditionally sit down and try to work out exactly how to architecture the whole thing, taking into account things like shooting, overwatch, etc before I sit down to code. TDD seems to suggest that I write the test, then the code, and then eventually get around to a new test that – had I known – would have influenced the code I wrote earlier. I can see that would have some benefits (for example, the Bowling Game Kata shows the change in design to a simpler one due to TDD) but I don’t know how likely it is that writing code just to pass the test will lead to a design path that isn’t optimal given later functionality requirements.

Maybe I am worrying about nothing, and the forced simplicity of design for TDD will ultimately lead to a good solution. Maybe I’m missing the point entirely, and there will be major refactoring down the road, and that’s a good thing. If that’s true, isn’t TDD going to end up taking a lot more time than is useful? Is there a secret, undocumented art to writing code following TDD where certain things are written first entirely to limit this sort of refactoring? If that’s the case, I may be handicapping myself by slavishly following the rulebook.

After reading the accepted answer on this StackOverflow question,  I’ve decided to push ahead with my TDD experiment. I need to understand the beast. I’ve got an idea of how certain things should be done, but we’ll see if it makes sense once I’ve laid the foundations using TDD. It’s going to be an interesting way to do the work!

This is a learning project I am documenting in order to teach myself TDD and unit testing – as I publish this, I have already written many parts in advance but I want to know what I’m doing wrong! If you see improvements or corrections, either to the code or the process, please leave a comment and let me know! Introduction to this project can be found here.