Download the code in C#
Download the code in Java
Check out my interview on .NET Rocks! – TDD on .NET and Java with Paul Mooney
For a brief overview, please refer to this post.
At this point, many tutorials start by launching into a “Hello, World” style tutorial, with very little practical basis.
This isn’t the most exciting concept, so let’s try a more practical example. Instead of churning out boring pleasantries, our application is going to do something a bit more interesting…build robots.
Specifically, our application is going to build awesome robots with big guns.
Ok, let’s get started with a narrative description of what we’re going to do.
“Mechs with Big Guns” is a factory that produces large, robotic vehicles designed to shoot other large, robotic vehicles. Robots are composed of several robotic parts, delivered by suppliers. Parts are loaded into a delivery bay, and are transported by worker drones to various rooms; functional parts such as arms, legs, etc., are dispatched to an assembly room. Guns and explosives are dispatched to an armoury.
The factory hosts many worker drones to assemble the robots. These drones will specialise in the construction of 1 specific robot, and will require all parts that make up that robot in order to build it. Once the drone has acquired all parts, it will enter the assembly room and build the robot. Newly built robots are transported to the armoury where waiting drones outfit them with guns. From time to time, two robots will randomly be selected from the armoury, and will engage one another in the arena, an advanced testing-ground in the factory. The winning robot will be repaired in the arena by repair drones. Its design will be promoted on a leader board, tracking each design and their associated victories.
The first thing we’ll do is look at the narrative again, this time highlighting each noun.
“Mechs with Big Guns” is a factory that produces large, robotic vehicles designed to shoot other large, robotic vehicles. Robots are composed of several robotic parts, delivered by suppliers. Parts are loaded into a delivery bay, and are transported by worker drones to various rooms; functional parts such as arms, legs, etc., are dispatched to an assembly room. Guns and explosives are dispatched to the armoury.
The factory hosts many worker drones to assemble the robots. These drones will specialise in the construction of 1 specific robot, and will require all parts that make up that robot in order to build it. Once the drone has acquired all parts, it will enter the assembly room and build the robot. Newly built robots are transported to the armoury where waiting drones outfit them with weapon assemblies. From time to time, two robots will randomly be selected from the armoury, and will engage one another in the arena, an advanced testing-ground in the factory. The winning robot will be repaired in the arena by repair drones. Its design will be promoted on a leader board, tracking each design and their associated victories.
Each highlight represents an actual object that will exist in our simulation.
“But where do I start with all of this?”
I recommend starting with a low-level component; that is, a component with little or no dependency on other objects. Supplier
looks like a good place to start. It doesn’t do much, other than deliver RobotParts
to a DeliveryBay
, so let’s start with that.
I’m assuming at this point, if you’re working with .NET, that you have a new Visual Studio solution and have created a Class Library project including NUnit as an included package. NUnit is the testing framework that we will use to validate our core components.
Or,if you’re working with Java, that you’ve set up a new Java 8 project in your IDE of choice, and have configured JUnit, the testing framework that we will use to validate our core components.
Add a class called RobotPartSupplierTests and add the following method:
C#
[Test] public void RobotPartSupplierDeliversRobotParts() { var mechSupplier = new RobotPartSupplier { RobotParts = new List<RobotPart> { new MockedRobotPart(), new MockedRobotPart() } }; var deliveryBay = new MockedDeliveryBay(); mechSupplier.DeliverRobotParts(deliveryBay); Assert.AreEqual(2, deliveryBay.RobotParts.Count); Assert.AreEqual(0, mechSupplier.RobotParts.Count); }
Java
@Test public void supplierDeliversRobotParts() { robotPartSupplier robotPartSupplier = new robotPartSupplier(); List<robotPart> robotParts = new ArrayList<robotPart>(); robotParts.add(new mockedRobotPart()); robotParts.add(new mockedRobotPart()); robotPartSupplier.setRobotPart(robotParts); deliveryBay mockedDeliveryBay = new mockedDeliveryBay(); robotPartSupplier.deliverRobotParts(mockedDeliveryBay); assertEquals(2, mockedDeliveryBay.getRobotParts().size()); assertEquals(0, robotPartSupplier.getRobotParts().size()); }
Here, we declare an instance of RobotPartSupplier, an implementation of the Supplier
abstract class:
C#
public abstract class Supplier { public List<RobotPart> RobotParts { get; set; } }
Java
public abstract class supplier { private List<robotPart> _robotParts; public List<robotPart> getRobotParts() { return _robotParts; } public void setRobotPart(List<robotPart> value) { _robotParts = value; } }
“Why create an abstraction? Can’t we just deal directly with the concrete implementation?”
Yes, we can. We’ve abstracted to the Supplier
class because at some point, we’re going to have to test a component that has a dependency on a RobotPartSupplier. The point of each test in a Test Driven project is that it tests exactly 1 unit of work, and no more. In our case, we’re testing to ensure that a RobotPartSupplier
can deliver RobotParts
to a DeliveryBay
. But if you follow the logic through, we’re also testing concrete implementations of DeliveryBay
and RobotPart
. This can have negative consequences later on.
What would happen, for example, if we were to change the underlying behaviour of DeliveryBay
? For starters, it would break associated DeliveryBay
tests.
“OK, so what?”
That’s fine – that’s what the tests are there for. But, it will also break our RobotPartSupplier
tests. The point is to isolate each problem domain into a set of tests, and to apply boundaries to those tests such that changes in other problem domains do not impact.
This might sound a bit pedantic. If so, bear with me. It actually provides a level of neatness to our code, which is particularly helpful as codebases grow larger.
Essentially, the rule of thumb is to abstract everything.
Notice that our RobotPartSupplier
contains a List of RobotPart
. Based on the above concept, The actual RobotPart
instances that are loaded in this test are mocked instance. These are dummy implementations of the core abstraction, RobotPart
, that will never make it to our actual core components.
“Why bother with them then?”
They provide simple implementations of dependent classes in order to protect our RobotSupplier
tests from changes in concrete implementations of those dependent classes.
As you can see, we’ve mocked DeliveryBay and RobotPart abstractions.
Let’s carry on. The first thing that we want to prove here is that our RobotPartSupplier
can deliver RobotParts
to a DeliveryBay
, so our test requires us to implement the DeliverRobotParts
method:
C#
public class RobotPartSupplier : Supplier { public void DeliverRobotParts(DeliveryBay deliveryBay) { deliveryBay.RobotParts = new List<RobotPart>(RobotParts); RobotParts.Clear(); } }
Java
public void deliverRobotParts(deliveryBay deliveryBay) { deliveryBay.setRobotPart(new ArrayList<robotPart>(this.getRobotParts())); getRobotParts().clear(); }
Notice that two things happen here:
- The
RobotPartSupplier’s
RobotParts
are copied to theDeliveryBay
- The
RobotPartSupplier’s
RobotParts
are emptied
As per the brief, this concludes our delivery-related functionality. Our test’s assertions determine that the RobotParts
have indeed been copied from our RobotPartSupplier to our mocked DeliveryBay.
In next week’s tutorial, we’ll continue our implementation, focusing on WorkerDrones
, and their functionality.
Connect with me: