Object Oriented, Test Driven Design in C# and Java: A Practical Example Part #1

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.

HelloWorld

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.

Robot

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 the DeliveryBay
  • 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:

RSSGitHubTwitter
LinkedInYouTubeGoogle+

7 thoughts on “Object Oriented, Test Driven Design in C# and Java: A Practical Example Part #1

  1. Russell Hammett Jr (@RLHammett)

    Hey, heard you for the first time on the dotnetrocks episode today so I thought I’d check out your blog, especially since you called out a real world example with robots! 🙂 I haven’t gotten into the second part of this post yet, but I was curious if you left out the Supplier definition purposely in this post? I get that you generally write your stubs first for TDD, but it looked like you started writing out your implementation for RobotPartSupplier, but never actually gave the Supplier class definition.

    Reply
    1. Paul Mooney Post author

      Hi Russell,

      Thanks for subscribing, and thanks for pointing that out. I provide a link to the complete code-listing in github at the beginning of each post. This includes all classes and unit-tests. For the sake of clarity, I’ve added the Supplier class definition to this post. I hope this helps.

      Reply
  2. Rob

    Many thanks for publishing such a thought provoking post. Apologies for such a big response, there’s a few things that I’ve been thinking about for a while which I thought I’d share, as their relevant to your post.

    Before starting with classifying the domain, I’d probably seek clarity on the requirements/scope of the required system. The description of business domain contains lots of useful information to help us with the design, but without requirements, there is no perspective from which to make design decisions.

    I’d probably make different design decisions when building a warehouse management, production workflow or business intelligence system. This is because the functional and non-functional requirements of such systems are likely to be different. For example, in a production workflow system, I might be tempted not to model supplier as a class. This is because, within the context of that type of system, the responsibilities you’ve given that class could be consolidated into another, without breaking the single responsibility principal. If I were to do this within a warehouse management system, this may well be inappropriate, as supplier may have greater significance. In short, the classification is an important judgement call that is difficult to make without some scope.

    The point I’m trying to get to is, be careful not to misuse TDD. It encourages you to think small and work up, attempting to protect you from becoming overwhelmed by the enormity of a project. My concerns is that if you make a series of verifyable low level decisions, in isolation of the system scope, it is unlikely that you will end up with the smallest possible solution.

    Consider the following analogy. Two people are dropped off in Edinburgh and asked to navigate to the Houses of Parliament in London.

    – The first person is overwhelmed with he task and can’t begin to comprehend all the road junctions and other navigation decisions he’ll have to make. He decides to make a start, resolving that he will need to plan and verify what he is doing every 20 mins. He feels good as he knows he is heading in the right direction and is making progress straight away.

    – The second traveller pauses for a while to think. He too is overwhelmed by the enormity of the task and can’t begin to comprehend all the options in detail. He’s thinking about planes, trains, buses, underground, hitchhiking and walking. After thinking for a while, he concludes that a short bus ride, flight and tube ride are probably going to be easiest for what he is tasked with. The bus/flight/tube plan is high level, the traveller is certain flights are regular, can be purchased on the day, he has enough money to cover all the fares and has a passport with him. However, he knows he will need to go into more detailed planning during each leg and that he will probably need to adjust the plan is anything unforeseen comes up. During each leg, the traveller also plans, travels and reviews the journey.

    Inevitably the second traveller arrives well before the first. The moral of the story is to do analysis and design at both the macro and micro level. Some level of macro thinking in not waterfall or un-agile! The Plan-do-review cycle is very important, but apply it to both the macro and micros level. TDD is a very helpful low level design tool. It also yields a rich suite of tests, the value of which cannot be overstated. However, don’t rely exclusively on TDD as your design tool, doing so is likely to send you on an unnecessarily long walk about!

    On the score of mocking/fakes and isolating collaborators, I’ve been involved in quite a few projects now where this approach has been mandatory. The general premise is that if the test fails, it should only fail for the SUT. I understand this argument, but have to question the amount of time spend building out mocks/fakes/doubles etc. My feeling is that we should be prepared to compromise here. If the collaborators of a class under test are predictable and quick to execute (e.g. do not depend on external info such as databases, web service, configuration calls) then I think that is a price worth paying. I know there’s a lot of folks out there that would classify this as an integration test. I don’t have a particular problem with the term if it’s helpful to distinguish between this kind of test and one in which all collaborators are strictly fakes. Interestingly Kent Beck (farther of TDD) and Martin Fowler suggest a new classification for unit tests (see “Is TDD Dead” series on YouTube) – the classification is Classicist and Mocktavist. The Classicist style, which is favoured by Kent and Martin, does not rely on a lot of faking, instead preferring to use real collaborators. If the test break because of one of the collaborators, I don’t think it’s such a big deal, I’ll be able to figure out what’s going on pretty quickly… In many cases I find real collaborators more useful, as it allows you to build up a deeper dependency graphs and explore more advanced scenarios. I often find Mocktavist tests tautological. It’s also worth flagging that a unit doesn’t necessarily mean a single method – it is down to the developer at the coal face to take the perspective of what s/he is working on an define the scope of the unit.

    Reply
    1. Paul Mooney Post author

      Thanks for your comment, Rob. Yes, the point of this series is not only to illustrate the advantages of TDD, but also to highlight its shortcomings. There’s an example of this in Part #4, where our logic breaks because we failed to think far enough ahead. The Big-Design-up-Front vs TDD debate rages on. Like most things, in my opinion, the correct solution lies somewhere in between the two.

      Reply
  3. Merritt Melker

    Sort of off topic but why:

    deliveryBay.RobotParts = new List(RobotParts);

    Doesn’t that mean every time a Robot Part Supplier delivers Robot Parts it removes all existing Robot Parts in the Delivery Bay?

    Reply

Leave a Reply

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

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Connecting to %s