Skip to content

Index

Telling the Story: The Pitfall of a Single Data Point

Let's say that you're sitting down to read a new book, and you come across the following:

The King's Knave Inn was but a short distance from the Alverston train depot, just outside the town proper. (excerpt from The Infernal Machine by John Lutz)

After reading this, a friend interrupts your reading and asks your thoughts on the book so far. What would you say?

Most likely, you'd respond that you need to read more, and it's still too early to decide if the book is good or not.

To honestly answer this question, you would need to read more of the book (ideally all of it) to get a full picture of the story.

When measuring an engineer's performance and effectiveness, why don't we take the same approach?

My experience has been that leaders look for one or more metrics to quantify a person. At the face of it, I understand why, as it's hard to compare people if there aren't numbers.

However, the mistake I see leaders make is what they're trying to measure. For example, do you measure the number of pull requests? What about the number of stories completed in a sprint? How about the number of bugs shipped to production? Something else entirely?

The problem is that even if you use all of the above (please don't do this), you're still not seeing the whole picture, but only bits and pieces. This would be like reading five chapters at random from a book and then giving an opinion.

The other problem with using metrics is that the measurement will cease to be effective as people will start gaming the system (see Goodhart's Law).

For example, if we measure effectiveness by the number of completed pull requests, then what stops someone from creating hundreds of single-line pull requests that don't accomplish anything?

On the other side, what about the engineer who reduced scope and time because they knew how to simplify the approach or came up with a more straightforward solution? This insight won't show up as a pull request or a completed story; however, this should be rewarded just the same.

To really determine how effective someone is, we need to look at things holistically, which can be done by examining how well someone does in these three areas:

  • Understanding the problem (e.g., why are we doing this?)
  • Understanding the system (e.g., how are we doing this?)
  • Understanding the people (e.g., whom are we doing this with?)

By looking into these areas, you will see what your team is good at and where they could use coaching, helping you be more effective. You might also realize that your team is doing things that aren't so obvious.

You can't write a report to generate these metrics. To understand this, you have to understand your team and how they work together. This involves paying attention, taking notes, and being engaged. Passive leaders will struggle if they use this approach.

Understanding the Problem (The Why)

To be successful, we first have to understand the problem that's being solved. Without this base knowledge, it's impossible to build the right solution or even ask the right question to the problem at hand.

How comfortable are they within the problem domain? Do they know certain terminologies, our customers, the users, workflows, and expected behaviors?

Besides quizzing, there may not be an obvious way to measure this; however, here's my approach.

First, you can look at the questions that are being asked. Are they surface level or are they deep? You can see these questions through chats and meetings, comments on the stories or pull requests, and interactions with others.

Second, look at the solution they came up with. Did they design it with domain knowledge in mind? For example, are things named correctly? Did their solution take care of the main workflows? What about the edge cases?

Third, how are they handling support issues? Being on support is a quick way of learning a problem domain and system. As such, I'm looking at how much help they need and how they communicate with others.

By using this approach, you can get a good sense of how knowledgeable someone is in the problem domain without quizzing them.

Understanding the System (The How)

There's always a push to deliver more things, and in order to do that, we have to understand the current system, its limitations, what's easy vs. what's hard, and from these constraints, determine the correct path to take.

In addition, once the system is live, we need to support it. If we don't know the moving parts, what it interacts with, and how it's used, we're going to have a bad time.

Like understanding the problem, we can measure system knowledge without quizzing them. In particular, I've found pull request comments and code reviews to be insightful on someone's knowledge of the system.

For example, do they call out that there's already something in the system that does this new piece of functionality? Do they suggest taking a simpler approach with what we have? Do they propose a different solution altogether because the system has a limitation? All of these are indicators of someone's system knowledge.

Another way to gauge system knowledge is by looking at how the person handles support requests. If you can understand the problem, find the cause, and create a fix, then by definition, you have to have a solid understanding of the system.

Understanding the People (The Who)

When it comes to the third part of being effective, we have to measure how they work with those around them. Most people think engineering is a solitary line of work, and that can be true when it comes to the development phase.

However, in reality, engineers work with others to design, develop, and iterate on a solution, and this can only happen when working with others. As such, building these relationships are paramount to being successful.

If you want to go fast, go alone. If you want to go far, go together.

Measuring team cohesion can be difficult (it could be its own post), however, we start simply be getting peer feedback on the person. We can also look at the communication between them and others through their comments, messages, or meetings.

Another way to measure this is through your company's recognition system. Whether it's an email or some other tool your company uses, you need to keep tabs on these recognitions, as you can use them as a talking point during 1:1s and review time.

Wrapping Up

So, how do we measure how effective someone is? We know that a single data point isn't sufficient and that if we limit ourselves to metrics, we can get a skewed sense of the person. To know, we have to take a holistic approach.

To accomplish this goal, I recommend measuring the following areas:

  • Understanding the problem (e.g., why are we doing this?)
  • Understanding the system (e.g., how are we doing this?)
  • Understanding the people (e.g., whom are we doing this with?)

In each of these areas, we can get a sense by observing their interactions they have, the questions they ask, the approaches they take, and how likely people want to work with the individual.

Three Steps to Better Interviews

At some point in your career, you're going to be conducting interviews. Regardless of the role, you have the opportunity to shape the future of the company as your recommendation controls whether this person is going to be a colleague or not.

What a lot of people don't realize is that an interview is can be the first experience that someone has with your company. As such, you want this experience to be fantastic, even if they're not hired, as they could be a future customer of yours.

With interviewing to be so important (there are whole books about the subject), it's confounding to me when companies don't invest in training or resources to help grow their leaders into being better interviewers. Especially, when making a bad hire can cause so much damage and is expensive to resolve in the long run.

Over my career, I've seen my share of good and bad interviews and have some tips and tricks to improve your interviewing skills. In this post, I'm going to share three tips that help me have better conversations with candidates.

1. Build Better Conversations Using Scenarios

The first mistake I seen interviewers make is that they have a set of questions that they want to pepper the candidate with, in an effort to figure out if they're going to be a good fit or not.

An ideal interview should flow more like a conversation where the candidate is getting to know you and the company and where you are learning about the candidate. As such, a never-ending list of questions makes the candidate feel like they're being interrogated and it doesn't allow for a natural conversation. A great interview should feel like tennis, each player receiving and sending the ball to the other side.

For example, let's say that I want to know a candidates familiarity with REST APIs. I could ask questions like

What's the difference between GET and POST?

What's the difference between a 404 and 400 response code?

Even though I'll get answers, this is not much of a conversation, but more of a quiz. Instead, I ask the following

In this scenario, I'm a newer engineer sitting down to make some changes to one of your APIs and I seem to be running into some issues.

For example, when I invoke the endpoint via GET, I'm getting back a 404 (Not Found). Doing some digging, it seems like it's related to the resource not being there, but I'm not sure how to troubleshoot. What would you recommend?

With the above, the candidate has a clear problem (e.g. can't communicate with the API) and has plenty of space to talk about what they're thinking (incorrect route, API not running, etc..). As the candidate is talking things through, I'm getting more insight on what they know and how they think about things. For example, if they mention that a firewall could be blocking the request, I could dig into that a bit more and learn that they have knowledge in networking or cloud technologies.

Another advantage of this approach is that we can add more steps. For example, here's one of the scenarios I ask to measure understanding of REST APIs.

In this scenario, I'm a newer engineer sitting down to make some changes to one of your APIs and I seem to be running into some issues.

For example, when I invoke the endpoint via GET, I'm getting back a 404 (Not Found). Doing some digging, it seems like it's related to the resource not being there, but I'm not sure how to troubleshoot. What would you recommend?

I've fixed the typo, made another request, and I'm now getting a 401 (Unauthorized). Looking up the response code, this implies that I don't have permissions, but I'm stuck on next steps. What would you recommend for troubleshooting?

Oh right, Bearer Token, I remember reading that in the README, but I didn't understand at the time. After generating the token and making another request, I'm now getting a 400 (Bad Request). Looking up the status code, it seems like it's something related to the payload or route. How would you troubleshoot?

Finally! After fixing that issue, I was able to get a 200 (Ok) response back, thanks for the help!

By using the above scenario, I can learn quite a bit about what systems an engineer has worked with, what gaps they might have, and how they troubleshoot issues. This is a lot more effective than knowing if an engineer can tell the difference between GET and POST.

2. Build Better Conversations Using Open-Ended Questions

Another common mistake I see is asking closed-ended questions to gauge knowledge. Even though these are binary in nature (Yes/No) or have a specific answer (What's the capital of North Carolina?), they come off as interogative instead of conversational. In addition, these types of questions are informational and could easily be looked up, where as open-ended questions are opinion based and come from experience.

For example, if we were to ask:

What's the difference between an Observable and a Promise?

We would know if the candidate knows the difference or not and that's about it. Even though this knowledge is helpful, we could learn this (and more) by rephrasing it to be open-ended instead.

For example, if we were to ask:

When would you use an Observable over a Promise?

With this question, not only do we learn if the candidate can talk about Observable vs Promise, but we also know if they know which scenarios to use one over the other.

For more effective questions, we could turn this question into a scenario, by asking the following

Let's say that we're working on a web component that has to call an API to get some data. It looks like we could call the API and have the value returned be either an Observable or a Promise. What would you recommend and why?

In this scenario, we get to learn if the candidate knows the differences between Observable and Promise, can reason about why one approach would be better than another, and explain that to another engineer. No matter their choice, we could follow up by asking why they wouldn't pick the other one option.

3. Build Better Conversations By Asking For Examples

For the final mistake, I see interviewers ask some form of a leading question, where based on the phrasing of the question, the candidate would be pressured or coerced into answering a particular way.

For example

This position involves mentoring interns to be associate engineers. Is that something you're comfortable with?

This is a leading question because if the candidate were to say "No", then they would believe that they wouldn't get the job. So they would always answer yes, regardless of how they feel, which makes this question useless as it doesn't tell us anything about the candidate. Most leading questions tend to also be close-ended questions, so a double strike for this style of interviewing.

But Cameron! I need to know this information as this person would be responsible for coaching up our engineers! Cool, then let's tell the candidate, but let's also provide some context and allow them to tell us their experience.

For example, we could phrase the question this way

One of the responsibilities for the role is to help grow interns into associate engineers so we can grow terrific engineers internally. With this context, can you walk us through a time where you had to coach someone up? What was your approach? What would you do differently?

With this question, you've still mentioned the skill you're looking for, however, you've added context on the "why" behind the question and you've set the candidate up to talk about their experience, which in turn, gives you more context about the person.

Thinking With Properties: Examining Where

Note: This post is for C# Advent Calendar 2020 organized by Matthew Groves. Check out some of the other posts that are happening over the month of December!

What Do We Mean By Properties?

When I think about software, I will generally think about the properties that the solution has to have Where properties are the characteristics that the code has. Sometimes, the property is obvious (for example, if you square a number, the result should always be positive). In this post, we’re going to look at LINQ’s Where method, examine some of the properties, and come up with a more efficient way of creating filters.

Examining LINQ's Where Method

For those not familiar with Where, it’s a method that promises that it will return a subset of a list Where each item fulfills some criteria (referred to as a predicate). The type signature for Where is

IEnumerable<T> => Func<T, bool> => IEnumerable<T>

At face value, this sounds pretty straightforward, but there are a few more properties that Where provides that aren’t obvious at first, but are beneficial

  • The results can’t be null (worse case, it’ll be an empty list because no item fulfilled the criteria)
  • The results can’t be larger than the original list
  • The results are in the same order that the original list was in (i.e. if we’re trying to find the even numbers in a list that is 1..10, then you’re guaranteed to get 2, 4, 6, 8, and 10. Not, 8, 2, 6, 10, 4)
  • The results will only contain elements that were in the original list (i.e. it can’t create elements out of thin air and it can’t copy elements in the original list)

Common LINQ Mistake with Where

That’s a ton of guarantees that you get from leveraging Where instead of doing your own filtering inside loops. With these properties in mind, let’s take a look at a common mistake that developers make when working with Where

1
2
3
4
5
6
7
8
9
// Generate numbers from -10 .. 100
var numbers = Enumerable.Range(-10, 111);

// Determines all positive numbers that are divisible by 6
var positiveDivisbleBySix = numbers
                            .Where(x=>x > 0) // iterates through the whole list (111 comparisons, returning 100 results)
                            .Where(x=>x % 2 == 0) // iterates through the new list (100 comparisons, returning 50 results)
                            .Where(x=>x % 3 == 0); // iterates through the new list (50 comparisons, returning 16 results)
// Overall metrics: 261 comparisons over 111 total elements)

By leveraging multiple Where statements, the list will be iterated once per statement. which may not be a big deal for small lists, but for larger lists, this will become a performance hit. In order to help cut down on the iterations, it’d be ideal to combine the multiple Where statements into a single one like so

1
2
3
4
// Generate numbers from -10 .. 100
var numbers = Enumerable.Range(-10, 111);

var positiveDivisibleBySix = numbers.Where(x => x > 0 && x % 2 == 0 && x % 3 == 0);

By combining the criteria in a single Where statement, we eliminate the multiple iteration problem, however, we introduce code that’s a bit harder to read and if we want to combine a non-fixed number of predicates, then this approach won’t work.

Since the goal is to take multiple predicates and combine them to a single predicate, my intuition is to leverage LINQ’s Aggregate method where we can take a List of items and reduce down to a single item.

Refactoring Multiple Where with Aggregate

In order to leverage Aggregate, we’ll first need to have a list of item to reduce down. Since all of the predicates are Func, we can easily create a List like so

1
2
3
4
Func<int, bool> isPositive = x => x > 0;
Func<int, bool> isEven = x => x % 2 == 0;
Func<int, bool> isDivisibleByThree = x => x % 3 == 0;
var predicates = new List<Func<int, bool>> {isPositive, isEven, isDivisibleByThree};

Now that we have a list of predicates, we can go ahead and start stubbing out the Aggregate call.

var combinedPredicate = predicates.Aggregate(...., ....);

In order to use Aggregate, we need to determine two pieces of information. First, what should the predicate be if there are no predicates to combine? Second, how do we we combine two predicates into a single predicate?

Defining the Base Case

When using Aggregate, the first thing that we need to think about is the base case, or in other words, what should the default value be if there are no elements to reduce down?

Given that the result needs to be a predicate, we know that the type should be Func<int, bool>, but how do we implement that? We’ve got one of two choices for the base case, we can either filter every item out (i.e. if no predicates are specified, then no items are kept) or we keep every item.

For our use case, we want to keep every item if there are no predicates, so our base case looks like the following

Func<int, bool> andIdentity = _ => true;

Defining How To Combine Predicates

Since we’re combining predicates, our combine function will need to have the following type

Func<int, bool> => Func<int, bool> => Func<int, bool>

1
2
3
4
Func<int, bool> combinedPredicateWithAnd(Func<int, bool> a, Func<int, bool> b)
{
  return x => ...;
}

With this in mind, we know that for an item to be valid, it has to match every predicate in the list which implies that we’ll be leveraging the && operator

1
2
3
4
Func<int, bool> combinedPredicateWithAnd(Func<int, bool> a, Func<int, bool> b)
{
  return x => ... && ...;
}

Now that we know to use &&, we can then use a and b to determine if the item is valid

1
2
3
4
Func<int, bool> combinedPredicateWithAnd(Func<int, bool> a, Func<int, bool> b)
{
  return x => a(x) && b(x);
}

Bringing It All Together

With the base case established and a way to combine predicates, here’s how we can solve the original problem.

// Define the predicates
Func<int, bool> isPositive = x => x > 0;
Func<int, bool> isEven = x => x % 2 == 0;
Func<int, bool> isDivisibleByThree = x => x % 3 == 0;
var predicates = new List<Func<int, bool>> {isPositive, isEven, isDivisibleByThree};

// Defining the Aggregate functions
Func<int, bool> andIdentity = _ => true;
Func<int, bool> combinedPredicateWithAnd(Func<int, bool> a, Func<int, bool> b)
{
  return x => a(x) && b(x);
}

// Combining the predicates
Func<int, bool> combinedAndPredicate = predicates.Aggregate(andIdentity, combinedPredicateWithAnd);

// The new solution
// Generate numbers from -10 .. 100
var numbers = Enumerable.Range(-10, 111);
var positiveDivisbleBySix = numbers.Where(combinedAndPredicate);

Going forward, if we need to add more predicates, all we need to do is to add it to the List and the rest of the application will work as expected

Wrapping Up

In this post, we explored LINQ’s Where method by examining its various properties. From there, we took a look at a common mistake developers make with Where and then showed how to resolve that issue by using Aggregate.

Shout out to Matthew Groves for letting me participate in C# Christmas (csadvent.christmas)

Mars Rover – Implementing Logger – Design

Welcome to the tenth installment of Learning Through Example – Mars Rover! At this point, we’ve wrapped up all the functionality for the Rover, so now it’s time to start implementing the logging requirements. Like always, we’ll first take at the requirements to ensure we’ve got a good idea of what’s needed. From there, we’ll talk about the different approaches for implementation we could take. Finally, we’ll decide on a rough approach for implementation

What Do We Need?

If we look back at the original requirements for logging, we find the following

In order to help troubleshoot failures with the emulation, every time a command is received, both the command received, the rover’s location, and the rover’s orientation should be logged.

Determining Intent From Vague Requirements

Yep, these requirements are clearly defined, but what’s the problem we’re trying to solve? Why do we care if this happens or not? After talking more with the Subject Matter Expert (SME), it seems like there are two main reasons for the logging requirement. First, they want a way to troubleshoot if something goes wrong in the emulation, and having this trace will be helpful in reproducing the error. Second, they want to have different runs be separated in the logging in the event they need to troubleshoot a specific run.

Going from Vague to Concrete Requirements

Given the above intent, it sounds like an easy approach we can take is to log all of this information to a file with a specific name. In real production code, you would likely use a logging solution instead of implementing your own, but this will provide a good opportunity to learn some of the System.IO namespace in .NET.

With a rough implementation in mind, we have another discussion with our SME, and come up with the following requirements:

  • When the application starts, the user will need to specify where to log the information to (known as the path)
  • If the path doesn’t exist, the user is informed of such and the application doesn’t continue
  • If the path isn’t accessible (maybe due to a permissions issue), then the user is informed of such and the application doesn’t continue
  • If the path is valid, then a text file will be created that contains the information generated by the emulator
  • Every time the Rover receives a command, its Location and Orientation should be logged

Software Design Guidelines

Now that we have requirements, how do we want to begin implementation? Do we want to add this logic to the Rover class? Or should we add a new component to handle logging?

Adding Logging to Rover

So one approach to this problem is to widen the responsibilities for the Rover, add a Log method and update the existing methods to use this new method. The advantage of this approach is that the logging is in one place so it’s easy to make changes. Another advantage is that since the Rover component already exists, we can go ahead and add some new tests pretty quick.

Unfortunately, this approach has two major downsides. First, by widening the responsibilities, we’ll need to update all existing tests to deal with logging (which may not be a trivial change to make). Second, since the Rover handles the logging, we’ve muddled our easy-to-test business rules with a cross-cutting concern and now the Rover would have a couple of different reasons to change. Bummer 🙁

Writing a New Component

The advantage of writing a new component is that we can focus on making a Logger component that just knows how to log any message to storage, regardless of what uses this component. By taking this approach, we can have a “dumb” component (almost not worth testing) and keep the things that we care about (i.e. the business rules) easier to test.

The main downside of this approach is that we have yet another component to manage. In our particular case, that’s alright, but as the codebase becomes larger, this is something to keep in mind.

Looking at Dependencies

With our rough approach in mind (creating a new component to handle logging), let’s take a look at what this new component is going to need to work as expected. Based on the requirements, the Logger will need to know where to log the information to (the path) and what to log (the message). These necessary pieces of information are known as dependencies because the Logger won’t work if these are not provided.

When it comes to injecting in a dependency, there are three main approaches, each providing benefits and drawbacks. Let’s take a closer look at these approaches.

Constructor Injection

First and foremost, constructor injection makes a lot of sense if the dependency doesn’t change throughout the lifetime of the object. Let’s say that we have an object that knows how to retrieve records from a database. As part of its work, it will need access to a connection string to connect to the database in question.

public class ItemRepository
{
  private readonly string _connectionString;

  // By adding the dependency here, there's no way you that
  // an ItemRepository could be created without specifying
  // a connection string.
  public ItemRepository(string connectionString)
  {
    _connectionString = connectionString;
  }
}

// Example usage
// If the connectionString isn't provided, then
// the code doesn't compile!
var itemRepository = new ItemRepository("Data Source=localhost;Initial Catalog=AdventureWorks;Integrated Security=True")

Generally speaking, an application doesn’t speak to multiple databases, so the odds of the connection string needing to change during the lifetime of this class is slim. Given that, it makes sense to inject this dependency at the constructor level.

One of the benefits of injecting dependencies at the constructor level is that you’re revealing your intent to other developers. In this case, you’re signaling others “Hey, if you’re going to use this class, you need to specify a connection string, otherwise this isn’t going to work!”

The main downside of this approach is that by adding more parameters to the constructor, you may end up with classes that have a ton of dependencies. However, if you find yourself in that situation, you might have another problem going on.

Method Injection

For this approach, method injection makes sense if the dependency can change during the lifetime of the object. Let’s say that we have a component that needs a way to send notifications and that the notification mechanism is based on user preferences. For example, if I’m making a purchase, then I want the order confirmation to be emailed to me whereas other customers might want their receipt texted to them.

public class PurchaseWorkflow
{
  // By adding the NotificationStrategy here,
  // there's no way that a developer can call 
  // the Complete method without specifying 
  // a way to notify the customer.
  public void Complete(Order order, INotificationStrategy strategy)
  {
    string receiptDetails = CreateReceipt(order);
    strategy.SendNotification(order.Customer, receiptDetails);
  }
}

// Example Usage
var order = new Order("Cameron", 22.50);
var workflow = new PurchaseWorkflow();
// If a NotificationStrategy isn't passed in, this code won't compile.
workflow.Complete(order, new TextNotificationStrategy()); 

If we were to use constructor injection, that means that we would need to instantiate a whole new object just because the notification mechanism needed to change which seems a bit excessive for our use case.

Otherwise, method injection has the same main advantage of constructor injection (i.e. revealing design intent) and disadvantage (can lead to a bunch of necessary parameters to pass in)

Property Injection

Unlike the other two approaches, property injection allows us to set the dependency as a property on the object and then start using the object. Unlike the other two approaches where you have to always specify the dependency, this approach allows you to set the dependency, use the class, then switch out the dependency without having to create a new instance (like constructor injection would force you to) and without having to specify it every time a method was called.

However, the main downside is that you as the developer need to remember to set this property otherwise the code will compile, but you’ll run into a runtime exception. It’s because of this limitation that I’ll avoid this injection technique and stick with either constructor or method level injections.

public class PurchaseWorkflow
{
  // By having the notification be set as a property, 
  // developer can set this dependency once and then
  // only change it when needed.
  // However, there's nothing forcing a developer to 
  // set this property.
  public INotificationStrategy NotificationStrategy {get; set;}

  public void Complete(Order order)
  {
    string receiptDetails = CreateReceipt(order);
    NotificationStrategy.SendNotification(order.Customer, receiptDetails);
  }
}

// The Mistake
var order = new Order("Cameron", 22.50);
var workflow = new PurchaseWorkflow();
workflow.Complete(order); // This will cause a NullReferenceException to be thrown

// Proper Usage
var order = new Order("Cameron", 22.50);
var workflow = new PurchaseWorkflow();
// As long as you remember to set this property, things should work!
workflow.NotificationStrategy = new TextNotificationStrategy();
workflow.Complete(order);

Designing the Logger

Now that we’ve looked at the various ways we can inject dependencies, we can start coming up with the rough design for Logger. Looking back at the requirements, we have two dependencies to worry about (the path and the message to log).

Given that the path is not going to change during a run of the emulator, we should leverage constructor injection for this dependency. For the message, since that will be based on the Rover's Location and Orientation which will change during the emulation, we should leverage method injection for this dependency.

With all of this in place, we now have a much clearer idea of how the Logger component is going to look!

Wrapping Up

In this post, we explored the requirements for logging and why the logging logic should be a separate component due it being a cross-cutting concern. From there, we explored at the three main ways to inject dependencies (constructor, method, and property). With this knowledge, we were able to make some smart decisions on how to inject where to log the information and what to log. In the next pose, we’ll build upon this design by creating the Logger component. From there, we were able to make decisions on how to inject the path of where to log and the message to log. In the next post, we’ll start implementing the logic for creating the Logger component.

Mars Rover – Implementing Rover – Turn Right

Welcome to the ninth installment of Learning Through Example – Mars Rover! In this post, we’re going to start driving out the functionality for the Rover and the business rules for turning right! First, we’ll take a look at the requirements to make sure we have an idea of what’s needed. From there, we’ll start implementing the requirements. By the end of this post, we’ll have a Rover that can do perform all of the commands!

Turning in Place By Turning Right

If we look back at the original requirements for turning right, we find this single line as the requirement

When the rover is told to turn right, it will rotate 90 degrees to the right, but not change its location

Given this requirement, we’re able to double-check with our Subject Matter Expert that the Rover is essentially rotating in place which yields the following requirements.

  • Given the Rover is facing North, when it turns right, then the Rover should be facing East at the same Coordinate
  • Given the Rover is facing East, when it turns right, then the Rover should be facing South at the same Coordinate
  • Given the Rover is facing South, when it turns right, then the Rover should be facing West at the same Coordinate
  • Given the Rover is facing West, when it turns right, then the Rover should be facing North at the same Coordinate

So far, the requirements are very similar to when we were implementing TurnLeft, so let’s see if we can leverage the same setup!

Red/Green/Refactor For Rover Facing North

Given what we learned when we were implementing the rules for TurnLeft, I’m going to go ahead and copy the test and add the single case for when Rover faces North

[Test]
[TestCase(Direction.North, Direction.East, TestName = "AndFacingNorthThenTheRoverFacesEast")]
public void RoverTurningRight(Direction start, Direction expected)
{
  var rover = new Rover { Orientation = start };
  var initialLocation = rover.Location;

  rover.TurnRight();

  Assert.AreEqual(expected, rover.Orientation);
  Assert.AreEqual(initialLocation, rover.Location);
}

Since TurnRight doesn’t exist, this test will fail so let’s go ahead and write enough code to make it pass. For the implementation, I’m going to go ahead and take advantage of the pattern that we found last time, yielding the following

1
2
3
4
5
6
7
8
9
public void TurnRight()
{
  Action ifNorth = () => Orientation = Direction.East;
  Action ifSouth = () => {};
  Action ifEast = () => {};
  Action ifWest = () => {};

  Execute(ifNorth, ifSouth, ifEast, ifWest);
}

One thing to note is to take a look at how we defined ifSouth, ifEast, and ifWest. Since we’re still wanting to implement one requirement at a time, I’ve defined them to do nothing so that if Execute is called, nothing will happen. Alternatively, I could have defined them to throw an exception, but I believe that’s a bit much since we’ll be implementing the functionality soon!

With that implementation, our test passes, so it’s time to go on to the next requirement!

Rapid Test Creation with TestCase

With a parameterized unit test in place, adding a new test is as simple as adding a new TestCase attribute.

[Test]
[TestCase(Direction.North, Direction.East, TestName = "AndFacingNorthThenTheRoverFacesEast")]
[TestCase(Direction.East, Direction.South, TestName = "AndFacingEastThenTheRoverFacesSouth")]
public void RoverTurningRight(Direction start, Direction expected)
{
  var rover = new Rover { Orientation = start };
  var initialLocation = rover.Location;

  rover.TurnRight();

  Assert.AreEqual(expected, rover.Orientation);
  Assert.AreEqual(initialLocation, rover.Location);
}

And updating ifEast action in the TurnRight method

1
2
3
4
5
6
7
8
9
public void TurnRight()
{
  Action ifNorth = () => Orientation = Direction.East;
  Action ifSouth = () => {};
  Action ifEast = () => Orientation = Direction.South;
  Action ifWest = () => {};

  Execute(ifNorth, ifSouth, ifEast, ifWest);
}

Now that we’ve proven how easy it is to make this change, let’s go ahead and knock out the final two test cases

[Test]
[TestCase(Direction.North, Direction.East, TestName = "AndFacingNorthThenTheRoverFacesEast")]
[TestCase(Direction.East, Direction.South, TestName = "AndFacingEastThenTheRoverFacesSouth")]
[TestCase(Direction.South, Direction.West, TestName = "AndFacingSouthThenTheRoverFacesWest")]
[TestCase(Direction.West, Direction.North, TestName = "AndFacingWestThenTheRoverFacesNorth")]
public void RoverTurningRight(Direction start, Direction expected)
{
  var rover = new Rover { Orientation = start };
  var initialLocation = rover.Location;

  rover.TurnRight();

  Assert.AreEqual(expected, rover.Orientation);
  Assert.AreEqual(initialLocation, rover.Location);
}

And implement the code to make those cases pass

1
2
3
4
5
6
7
8
9
public void TurnRight()
{
  Action ifNorth = () => Orientation = Direction.East;
  Action ifSouth = () => Orientation = Direction.West;
  Action ifEast = () => Orientation = Direction.South;
  Action ifWest = () => Orientation = Direction.North;

  Execute(ifNorth, ifSouth, ifEast, ifWest);
}

Goodness, that was a pretty quick implementation of TurnRight! Thanks to our previous refactoring in Rover and leveraging parameterized testing, we were able to implement this new feature with a minimal amount of code and the code we did write was focused on the new functionality, not boilerplate.

With that being said, here’s our final version of Rover!

public class Rover
{
  public Direction Orientation { get; set; }
  public Coordinate Location { get; set; }

  public Rover()
  {
    Orientation = Direction.North;
    Location = new Coordinate();
  }

  public void MoveForward()
  {
    Action ifNorth = () => Location=Location.AdjustYBy(1);
    Action ifSouth = () => Location=Location.AdjustYBy(-1);
    Action ifEast = () => Location=Location.AdjustXBy(1);
    Action ifWest = () => Location=Location.AdjustXBy(-1);

    Execute(ifNorth, ifSouth, ifEast, ifWest);
  }

  public void MoveBackward()
  {
    Action ifNorth = () => Location=Location.AdjustYBy(-1);
    Action ifSouth = () => Location=Location.AdjustYBy(1);
    Action ifEast = () => Location=Location.AdjustXBy(-1);
    Action ifWest = () => Location=Location.AdjustXBy(1);

    Execute(ifNorth, ifSouth, ifEast, ifWest);
  }

  public void TurnLeft()
  {
    Action ifNorth = () => Orientation = Direction.West;
    Action ifWest = () => Orientation = Direction.South;
    Action ifSouth = () => Orientation = Direction.East;
    Action ifEast = () => Orientation = Direction.North;

    Execute(ifNorth, ifSouth, ifEast, ifWest);
  }

  public void TurnRight()
  {
    Action ifNorth = () => Orientation = Direction.East;
    Action ifEast = () => Orientation = Direction.South;
    Action ifSouth = () => Orientation = Direction.West;
    Action ifWest = () => Orientation = Direction.North;

    Execute(ifNorth, ifSouth, ifEast, ifWest);
  }

  private void Execute(Action ifNorth, Action ifSouth, Action ifEast, Action ifWest)
  {
    switch(Orientation)
    {
      case Direction.North: ifNorth(); break;
      case Direction.South: ifSouth(); break;
      case Direction.East: ifEast(); break;
      case Direction.West: ifWest(); break;
    }
  }
}

Wrapping Up

With the completion of TurnRight, the Rover has finally been implemented with all required pieces of functionality. Through this process, we learned how to write good unit tests, how to refactor unit tests to use parameterized unit testing, and reducing repetition through a SOLID refactor of if/else all of which culminating in TurnRight being a simple piece of functionality to include. In the next post, we will start working on implementing our logger!

Mars Rover – Implementing Rover – Refactoring Rover

Welcome to the eighth installment of Learning Through Example – Mars Rover! In this post, we’re going to examine the Rover class and see what refactoring we can do based on some patterns we’re seeing with MoveForward, MoveBackward, and TurnLeft. After looking at the characteristics, we’ll explore a couple of different approaches with their pros and cons. Finally, we’ll make the refactor, using our test suite to make sure we didn’t regress in functionality.

What’s The Problem

If we look at the definition for Rover, it becomes clear that we have some major code duplication going on with regards to its MoveForward, MoveBackward, and TurnLeft methods.

public class Rover
{
  public Direction Orientation {get; set;}
  public Coordinate Location {get; set;}

  public Rover()
  {
    Orientation = Direction.North;
    Location = new Coordinate {X=0, Y=0};
  }

  public void MoveForward()
  {
    if (Orientation == Direction.North) {
      Location = Location.AdjustYBy(1);
    }
    if (Orientation == Direction.South) {
      Location = Location.AdjustYBy(-1);
    }
    if (Orientation == Direction.East) {
      Location = Location.AdjustXBy(1);
    }
    if (Orientation == Direction.West) {
      Location = Location.AdustXBy(-1);
    }
  }

  public void MoveBackward()
  {
    if (Orientation == Direction.North) {
      Location = Location.AdjustYBy(-1);
    }
    if (Orientation == Direction.South) {
      Location = Location.AdjustYBy(1);
    }
    if (Orientation == Direction.East) {
      Location = Location.AdjustXBy(-1);
    }
    if (Orientation == Direction.West) {
      Location = Location.AdjustXBy(1);
    }
  }

  public void TurnLeft()
  {
    if (Orientation == Direction.North) {
      Orientation = Direction.West;
    }
    else if (Orientation == Direction.West) {
      Orientation = Direction.South;
    }
    else if (Orientation == Direction.South) {
      Orientation = Direction.East;
    }
    else if (Orientation == Direction.East) {
      Orientation = Direction.North;
    }
  }
}

Based on the implementations, it seems like knowing what Direction the Rover is facing is a key rule to determine what update the Rover needs to do. The big pain point with this block of statements is that if we added a new Direction (like NorthEast), then we’d have to update these statements in multiple places even though all of these instances are referring to the same concept (i.e is the Rover facing this Direction). The other, more nuanced issue is that if we added a new Direction, there’s nothing forcing us to update all of these places because of our use of if/else.

How To Resolve?

From what we’re seeing, the primary issue are the duplicated if/else statements, so one of our primary design goals should be to have that logic in one place (instead of three different places). From there, we’d also like to have a way for the compiler to force us to handle the different Directions that the Rover is facing.

Isolating the if/else

In order to isolate the if/else, let’s go ahead and extract the logic to a new private method

private xxx Execute(xxxx)
{
  if (Orientation == Direction.North) {
    //
  }
  else if (Orientation == Direction.South) {
    // 
  }
  else if (Orientation == Direction.East) {
    //
  }
  else if (Orientation == Direction.West) {
    //
  }
}

We’ve now got the if/else isolated, but there are some questions on what the return type of this method should be or what parameters it will take. In order to answer those questions, let’s take a look at a couple of different approaches.

Using a Functional Approach

Now that we have a method that can operate given the Rover's Orientation, one approach we could do is to modify this method to take in an Action for every possible Orientation. Then based on the Orientation, the appropriate Action is called.

private void Execute(Action ifNorth, Action ifSouth, Action ifEast, Action ifWest)
{
  if (Orientation == Direction.North) {
    ifNorth();
  }
  else if (Orientation == Direction.South) {
    ifSouth();
  }
  else if (Orientation == Direction.East) {
    ifEast();
  }
  else if (Orientation == Direction.West) {
    ifWest();
  }
}

With this implementation, TurnLeft would look like

1
2
3
4
5
6
7
8
9
public void TurnLeft()
{
  Action ifNorth = () => Orientation = Direction.West;
  Action ifWest = () => Orientation = Direction.South;
  Action ifSouth = () => Orientation = Direction.East;
  Action ifEast = () => Orientation = Direction.North;

  Execute(ifNorth, ifSouth, ifEast, ifWest);
}

And MoveForward would look like

1
2
3
4
5
6
7
8
9
public void MoveForward()
{
  Action ifNorth = () => Location=Location.AdjustYBy(1);
  Action ifSouth = () => Location=Location.AdjustYBy(-1);
  Action ifEast = () => Location=Location.AdjustXBy(1);
  Action ifWest = () => Location=Location.AdjustXBy(-1);

  Execute(ifNorth, ifSouth, ifEast, ifWest);
}
Advantages

The primary advantage of this approach is that in order to call Execute, you have to pass a parameter for the different directions. If you fail to do so, the code will fail to compile, which forces developers to handle the various use cases.

Furthermore, if there’s a bug in any of the Actions, then for troubleshooting, we’d need to know what method caused the problem and what the rover’s Orientation was and we can quickly figure out what the problem is.

Drawbacks

When using this approach, one thing to keep in mind is if we need to support additional Directions in the future. The Execute method is already taking in four parameters and keeping them in the right order is difficult enough. What about five, six, or ten directions to support? The function signature would quickly become unwieldy.

In addition, the other downside to this approach is that if any of the Actions were to become much more complicated, it would start cluttering up the respective Move or Turn methods.

Using an Object-Oriented Approach

Now that we have a method that can operate given the Rover‘s Orientation, another approach is to introduce the Strategy pattern where we would need to create the following types

  • IMovementStrategy – an interface that lists the different kinds of movements that can be done (currently MoveForward, MoveBackward, and TurnLeft)
  • NorthMovementStrategy – a new class that implements the IMovementStrategy and is responsible for the various business rules when moving and facing North
  • GetMovementStrategy – a method in Rover that for the current Orientation, returns the right implementation of IMovementStrategy
  • Update the existing move methods to use GetMovementStrategy

First, let’s take a look at the IMovementStrategy definition

1
2
3
4
5
6
internal IMovementStrategy()
{
  Coordinate MoveForward(Coordinate coordinate);
  Coordinate MoveBackward(Coordinate coordinate);
  Direction TurnLeft();
}

The key takeaway for this type is that if we need to add an additional movement (like TurnRight), we’ll need to update this interface. Taking a closer look, we’ve defined the signatures for all of the methods to return a value (either a Location or a Direction). We have to make this change because no Strategy will have access to the Rover itself, so it’ll need to return the correct value for Rover to hold onto.

Next, let’s take a look at an example implementation for when the Orientation is North

internal class NorthMovementStrategy : IMovementStrategy
{
  public Coordinate MoveForward(Coordinate coordinate)
  {
    return coordinate.AdjustYBy(1);
  }
  public Coordinate MoveBackward(Coordinate coordinate)
  {
    return coordinate.AdjustYBy(-1);
  }
  public Direction TurnLeft()
  {
    return Direction.West;
  }
}

If you look closer, we’ve essentially moved the business rules for the three methods from the existing business rules. We would repeat this process for the other Directions (yielding a SouthMovementStrategy, an EastMovementStrategy, and a WestMovementStrategy)

Now that we have the various strategies in place, we can create the GetMovementStrategy method

private IMovementStrategy GetStrategy()
{
  if (Orientation == Direction.North) {
   return new NorthMovementStrategy();
  }
  else if (Orientation == Direction.South) {
    return new SouthMovementStrategy();
  }
  else if (Orientation == Direction.East) {
    return new EastMovementStrategy();
  }
  else if (Orientation == Direction.West) {
    return new WestMovementStrategy();
  }
}

Which then would allow us to refactor TurnLeft as the following

1
2
3
4
5
public void TurnLeft()
{
  var movementStrategy = GetMovementStrategy();
  Orientation = movementStrategy.TurnLeft();
}

And MoveForward as

1
2
3
4
5
public void MoveForward()
{
  var movementStrategy = GetMovementStrategy();
  Location = movementStrategy.MoveForward(Location);
}
Advantages

When using this approach, we can easily extend functionality when new Directions are added. For example, if we added NorthEast, then we would create a new MovementStrategy, have it implement the interface and fill in business rules (also allowing us to easily add tests as we go). From there, we would update the GetMovementStrategy method to return the new strategy. By adding new functionality by primarily writing new code and making very little modifications to existing code, we can adhere to the Open/Closed Principle from SOLID design.

Drawbacks

The major drawback of this approach is that we had to create a lot of boilerplate code to make everything hang. One interface, four implementations, and a factory method for creating the appropriate strategy for a given Orientation.

The other, more subtle, drawback is that one must be aware of the pivot point of change. For example, we originally made this refactor based on strategies of what to do when faced in a given Direction. This made sense as an easy refactor. However, we’ve now made it easy to support a new Direction when added, but if we were to add a new Command instead, we would update the IMovementStrategy with the new method which would break all implementations. Once that happens, we have no choice but to implement the whole feature at once.

Personally, I’m a proponent of delivering value in small chunks and verify that we’re building the right thing, but in this case, this approach doesn’t make that easy.

Start the Refactor

Given the above options, I’m going to choose the more functional approach mostly because the business rules in my case are one-liners. If they were more complex, then I’d lean heavily more towards the Strategy pattern.

With that in mind, I’m going to go ahead and define the general Execute method as such

private void Execute(Action ifNorth, Action ifSouth, Action ifEast, Action ifWest)
{
  if (Orientation == Direction.North) {
    ifNorth();
  }
  else if (Orientation == Direction.South) {
    ifSouth();
  }
  else if (Orientation == Direction.East) {
    ifEast();
  }
  else if (Orientation == Direction.West) {
    ifWest();
  }
}

Once that’s in place, I can go ahead and refactor TurnLeft to use the Execute method.

1
2
3
4
5
6
7
8
9
public void TurnLeft()
{
  Action ifNorth = () => Orienation = Direction.West;
  Action ifWest = () => Orientation = Direction.South;
  Action ifSouth = () => Orientation = Direction.East;
  Action ifEast = () => Orientation = Direction.North;

  Execute(ifNorth, ifSouth, ifEast, ifWest);
}

Now that I’ve updated TurnLeft, I can run the tests to make sure I didn’t break functionality. This is where the power of automated tests come to play. I can have higher confidence that my refactor didn’t break anything because of the test suite.

After verifying that the tests pass for TurnLeft, I’ll continue refactoring MoveForward

1
2
3
4
5
6
7
8
9
public void MoveForward()
{
  Action ifNorth = () => Location=Location.AdjustYBy(1);
  Action ifSouth = () => Location=Location.AdjustYBy(-1);
  Action ifEast = () => Location=Location.AdjustXBy(1);
  Action ifWest = () => Location=Location.AdjustXBy(-1);

  Execute(ifNorth, ifSouth, ifEast, ifWest);
}

and MoveBackward using the same technique

public void MoveForward()
{
  Action ifNorth = () => Location=Location.AdjustYBy(1);
  Action ifSouth = () => Location=Location.AdjustYBy(-1);
  Action ifEast = () => Location=Location.AdjustXBy(1);
  Action ifWest = () => Location=Location.AdjustXBy(-1);

  Execute(ifNorth, ifSouth, ifEast, ifWest);
}

public void MoveBackward()
{
  Action ifNorth = () => Location=Location.AdjustYBy(-1);
  Action ifSouth = () => Location=Location.AdjustYBy(1);
  Action ifEast = () => Location=Location.AdjustXBy(-1);
  Action ifWest = () => Location=Location.AdjustXBy(1);

  Execute(ifNorth, ifSouth, ifEast, ifWest);
}

One Final Touch

So at this point, we’ve successfully refactored MoveForward, MoveBackward, and TurnLeft to use Execute instead of their own logic, but the more I look at Execute, the more I want to convert the if/else into a switch statement because a switch is going to force us to implement the different Directions whereas the if/else pattern did not. So let’s go ahead and make that change.

public void Execute(Action ifNorth, Action ifSouth, Action ifEast, Action ifWest)
{
  switch(Orientation)
  {
    case Direction.North: ifNorth(); break;
    case Direction.South: ifSouth(); break;
    case Direction.East: ifEast(); break;
    case Direction.West: ifWest(); break;
  }
}

And we can verify that our refactored worked like a top!

Wrapping Up

At this point, we’ve made some good changes to the Rover with how the various methods work by examining the duplication (the if/else), extracting the duplication to a single area, and the compared the functional approach and the OOP approach via the Strategy pattern. In the next post, we’ll take on implementing TurnRight and see how our new refactor works in practice when adding new functionality!

Mars Rover – Implementing Rover : Turning Left

Welcome to the seventh installment of Learning Through Example – Mars Rover! In this post, we’re going to start driving out the functionality for the Rover and the business rules for turning left! First, we’ll take a look at the requirements to make sure we have an idea of what’s needed. From there, we’ll start implementing the requirements. By the end of this post, we’ll have a Rover that can do 3/4 of the possible commands!

Turning in Place By Turning Left

If we look back at the original requirements for turning left, we find this single line as the requirement

When the rover is told to turn left, it will rotate 90 degrees to the left, but not change its location

Given this requirement, we’re able to double check with our Subject Matter Expert that the Rover is essentially rotating in place which yields the following requirements.

  • Given the Rover is facing North, when it turns left, then the Rover should be facing West at the same Coordinate
  • Given the Rover is facing West, when it turns left, then the Rover should be facing South at the same Coordinate
  • Given the Rover is facing South, when it turns left, then the Rover should be facing East at the same Coordinate
  • Given the Rover is facing East, when it turns left, then the Rover should be facing North at the same Coordinate

Even though we’ve not written tests that have this exact setup, it looks like we should be able to use that as a start while we’re implementing. So let’s go ahead tackle the first requirement!

Red/Green/Refactor For Rover Facing North

Leaning on lessons learned previously, we can write the following test for when the Rover is facing North.

[Test]
public void AndFacingNorthThenTheRoverFacesWest()
{
  var rover = new Rover {Orientation=Direction.North};
  var initialLocation = rover.Location;

  rover.TurnLeft();

  Assert.AreEqual(initialLocation, rover.Location);
  Assert.AreEqual(Direction.West, rover.Orientation);
}

Not too bad of a setup! First, we create the Rover and capture its initialLocation. From there, we call our new method, TurnLeft which doesn’t exist yet, but will soon. After TurnLeft has been called, we check to make sure that our Location is the same and that our Orientation has been updated accordingly.

Now let’s write just enough code to pass.

1
2
3
4
5
6
public void TurnLeft()
{
  if (Orientation == Direction.North) {
    Orientation = Direction.West;
  }
}

Pretty straightforward implementation and is good enough to make the test pass. Not much to refactor at this point so let’s go ahead and work on the next test case.

Red/Green/Refactor For Rover Facing West

For the next test, let’s go ahead and write one for when the Rover faces West

[Test]
public void AndFacingWestThenTheRoverFacesSouth()
{
  var rover = new Rover {Orientation=Direction.West};
  var initialLocation = rover.Location;

  rover.TurnLeft();

  Assert.AreEqual(initialLocation, rover.Location);
  Assert.AreEqual(Direction.South, rover.Orientation);
}

And now enough code to make it pass

1
2
3
4
5
6
7
8
9
public void TurnLeft()
{
  if (Orientation == Direction.North) {
    Orientation = Direction.West;
  }
  else if (Orientation == Direction.West) {
    Orientation = Direction.South;
  }
}

Once again, now that we have passing tests, is there anything we want to refactor? The business rules look to be simple enough so I don’t feel the need to refactor those. When we look at the test code, the test seems straightforward and I’m not sure what I’d simplify.

Time to commit these changes and on to the next requirement.

Red/Green/Refactor For Rover Facing South

Third verse same as the first, we can write a failing test for when the Rover faces South.

[Test]
public void AndFacingSouthThenTheRoverFacesEast()
{
  var rover = new Rover {Orientation=Direction.South};
  var initialLocation = rover.Location;

  rover.TurnLeft();

  Assert.AreEqual(initialLocation, rover.Location);
  Assert.AreEqual(Direction.East, rover.Orientation);
}

And now enough code to make it pass

public void TurnLeft()
{
  if (Orientation == Direction.North) {
    Orientation = Direction.West;
  }
  else if (Orientation == Direction.West) {
    Orientation = Direction.South;
  }
  else if (Orientation == Direction.South) {
    Orientation = Direction.East;
  }
}

With everything passing again, we can pause to rethink about refactoring but so far so good from my perspective. There are maybe some superficial changes that could be made, but I can’t make a strong enough argument to implement them.

Time to commit and tackle the last requirement!

Red/Green/Refactor for Rover Facing East

Let’s go ahead and write the final test for when the Rover faces East

[Test]
public void AndFacingEastThenTheRoverFacesNorth()
{
  var rover = new Rover {Orientation=Direction.East};
  var initialLocation = rover.Location;

  rover.TurnLeft();

  Assert.AreEqual(initialLocation, rover.Location);
  Assert.AreEqual(Direction.North, rover.Orientation);
}

And now enough code to make it pass

public void TurnLeft()
{
  if (Orientation == Direction.North) {
    Orientation = Direction.West;
  }
  else if (Orientation == Direction.West) {
    Orientation = Direction.South;
  }
  else if (Orientation == Direction.South) {
    Orientation = Direction.East;
  }
  else if (Orientation == Direction.East) {
    Orientation = Direction.North;
  }
}

There Seems To Be a Pattern

Now that we have a few tests in place, it seems like all of the tests are following a pretty straightforward pattern:

  1. Create a Rover with a particular Orientation
  2. Get the starting Location
  3. Call TurnLeft
  4. Verify that the Location didn’t change
  5. Verify that the Orientation is correct

Parameterized Testing

When I find myself writing tests where the tests look exactly the same and the only difference is the initial data and expected output, then I start thinking about how to parameterize the test so that the test is run multiple times, but with different parameters. By making this change, we reduce the amount of test code written, without sacrificing readability or maintainability.

When To Not Parameterize

It seems like it would be easy to write tests in this fashion, so why don’t we use this technique all the time?

The primary reason why we wouldn’t want to use this approach is if we would need to pass in a ton of parameters in order to make this test generic enough. A general guideline I use is that if the total parameters are three or less, then parameterization is a good fit. However, if I find out that I need to pass in more than three parameters, it makes me wonder if the tests are really the same test at all.

A secondary reason why this approach may not be a good fit is if it clouds our readability and debug-ability of the tests. Recall that our tests help us drive out our requirements and helps us talk in a ubiquitous language. If we can no longer do that easily, then we should not parameterize our tests.

Refactoring

With all of that said, let’s take a look at our tests for WhenTurningLeft and see what each of the tests have in common.

[TestFixture]
public class WhenTurningLeft
{
  [Test]
  public void AndFacingNorthThenTheRoverFacesWest()
  {
    var rover = new Rover {Orientation=Direction.North};
    var initialLocation = rover.Location;

    rover.TurnLeft();

    Assert.AreEqual(initialLocation, rover.Location);
    Assert.AreEqual(Direction.West, rover.Orientation);
  }

  [Test]
  public void AndFacingWestThenTheRoverFacesSouth()
  {
    var rover = new Rover {Orientation=Direction.West};
    var initialLocation = rover.Location;

    rover.TurnLeft();

    Assert.AreEqual(initialLocation, rover.Location);
    Assert.AreEqual(Direction.South, rover.Orientation);
  }

  [Test]
  public void AndFacingSouthThenTheRoverFacesEast()
  {
    var rover = new Rover {Orientation=Direction.South};
    var initialLocation = rover.Location;

    rover.TurnLeft();

    Assert.AreEqual(initialLocation, rover.Location);
    Assert.AreEqual(Direction.East, rover.Orientation);
  }

  [Test]
  public void AndFacingEastThenTheRoverFacesNorth()
  {
    var rover = new Rover {Orientation=Direction.East};
    var initialLocation = rover.Location;

    rover.TurnLeft();

    Assert.AreEqual(initialLocation, rover.Location);
    Assert.AreEqual(Direction.North, rover.Orientation);
  }
}

If we examine a bit closer, we’ll notice that the only differences are the starting Orientation and the expected Orientation. Since those are the only values that we would need to parameterize, this is a great candidate for parameterization!

So starting off slow, we’re going to refactor the AndFacingEastThenTheRoverFacesNorth test to use NUnit’s TestCase attribute to pass in the parameters. Even though we’re using NUnit for this functionality, most test frameworks support this concept, just with different syntax.

[Test]
[TestCase(Direction.East, Direction.North, TestName = "AndFacingEastThenTheRoverFacesNorth")]
public void RoverTurningLeft(Direction start, Direction expected)
{
  var rover = new Rover { Orientation = start };
  var initialLocation = rover.Location;

  rover.TurnLeft();

  Assert.AreEqual(expected, rover.Orientation);
  Assert.AreEqual(initialLocation, rover.Location);
}

With these changes in place, let’s break down what our approach was.

First, we added the TestCase attribute to the test as this allows us to specify parameters. In this case, we’re passing in two parameters (Direction.East and Direction.North). In addition, we’re also giving this TestCase a unique test name by setting the TestName property to AndFacingEastThenTheRoverFacesNorth. By setting this property, we’re controlling what this test will show up in the test runner.

Second, we changed the signature of the test method to take in two Directions, one for the start and the other for the expected direction for Rover. These new parameters line up with the ordering of parameters in the TestCase. In addition, since this test is going to be a bit more generic, I renamed this method to RoverTurningLeft because the TestName is going to have my requirement and this method is the generic shell.

Finally, we updated how we initialized the Rover by setting it’s Orientation to be start and we changed our Assert to be on expected.

Verifying The Changes

With this new test in place, let’s run it and verify that our changes worked

Single test passing for when rover faces north)

Single test passing for when Rover faces North

Nice! Let’s go ahead and add additional TestCase attributes for the other tests

[Test]
[TestCase(Direction.North, Direction.West, TestName = "AndFacingNorthThenTheRoverFacesWest")]
[TestCase(Direction.West, Direction.South, TestName = "AndFacingWestThenTheRoverFacesSouth")]
[TestCase(Direction.South, Direction.East, TestName = "AndFacingSouthThenTheRoverFacesEast")]
[TestCase(Direction.East, Direction.North, TestName = "AndFacingEastThenTheRoverFacesNorth")]
public void RoverTurningLeft(Direction start, Direction expected)
{
  var rover = new Rover { Orientation = start };
  var initialLocation = rover.Location;

  rover.TurnLeft();

  Assert.AreEqual(expected, rover.Orientation);
  Assert.AreEqual(initialLocation, rover.Location);
}

And if we run our test suite, we verify that everything passes!

Full test suite passing)

All four tests passing

With this in place, we can remove the other tests in this file, which yields the following test class.

[TestFixture]
public class WhenTurningLeft
{
  [Test]
  [TestCase(Direction.North, Direction.West, TestName = "AndFacingNorthThenTheRoverFacesWest")]
  [TestCase(Direction.West, Direction.South, TestName = "AndFacingWestThenTheRoverFacesSouth")]
  [TestCase(Direction.South, Direction.East, TestName = "AndFacingSouthThenTheRoverFacesEast")]
  [TestCase(Direction.East, Direction.North, TestName = "AndFacingEastThenTheRoverFacesNorth")]
  public void RoverTurningLeft(Direction start, Direction expected)
  {
    var rover = new Rover { Orientation = start };
    var initialLocation = rover.Location;

    rover.TurnLeft();

    Assert.AreEqual(expected, rover.Orientation);
    Assert.AreEqual(initialLocation, rover.Location);
  }
}

Already we can see how easy it would be to extend this test suite if a new Direction were to be added by adding the new TestCase and updating the other ones as needed.

Wrapping Up

Goodness, just like that we have a Rover that knows how to MoveForward, MoveBackward, and TurnLeft! In this post, we added some new functionality to Rover by first examining the requirements and implementing them one at a time. From there, we noticed during our refactor step that our test code looked similar, just differing on inputs. This, in turn, inspired us to look at parameterized testing which allowed us to drastically reduce the amount of code needed for the various use cases and allows us to add additional cases easier in the future. In the next post, we start taking a look at some interesting patterns that Rover is exhibiting in its three methods.

Mars Rover – Implementing Rover : Moving Backward

Welcome to the sixth installment of Learning Through Example – Mars Rover! In this post, we’ll pick up from where we left off with Rover and start implementing the rules for the Rover to move backward!

Just like last time, we’ll first examine what it means for the Rover to move backward by looking over the requirements in deeper detail. Once we have a better understanding, we’ll start driving out the functionality by focusing on a simple case and building more complexity. By the end of this post, we’ll have a Rover that will know how to move backward when facing any Direction!

So What Does It Mean To Move Backward?

If we look back at the original requirements for moving backward, we find this single line with regards to moving backward

When the rover is told to move backward, then it will move rover unit away from the direction it’s facing

Hmm, based on our previous experience with implementing MoveForward, I have a good idea of the requirements and after double-checking with our Subject Matter Expert, we confirm that our assumption is correct and derive the following requirements.

  • Given the Rover is facing North, when it moves forward, then its Y value decreases by 1
  • Given the Rover is facing South, when it moves forward, then its Y value increases by 1
  • Given the Rover is facing East, when it moves forward, then its X value decreases by 1
  • Given the Rover is facing East, when it moves forward, then its X value increases by 1

Great, we have enough information to get started so we can start demonstrating the software and get quicker feedback!

Red/Green/Refactor For Rover Facing North

Given the lessons learned when writing tests for when implementing move forward, we can write the following test.

[Test]
public void AndFacingNorthThenYDecreasesByOne()
{
  var rover = new Rover { Orientation = Direction.North };
  var initialLocation = rover.Location;

  rover.MoveBackward();

  var expectedLocation = new Coordinate(initialLocation.X, initialLocation.Y - 1);
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.North, rover.Orientation);
}

Now let’s write just enough code to pass.

1
2
3
4
public void MoveBackward()
{
  Location=Location.AdjustYBy(-1);
}

Not too shabby, we’ve got enough code in place to make everything pass so let’s take a look at refactoring. From the business rules, MoveBackward is too simple to need a refactor, though I have a suspicion about what the final look of the method will look like. From the test code, the test is pretty straightforward and looks a ton like the tests we wrote for MoveForward.

With all of that in mind, let’s go ahead and commit these changes and go to the next requirement!

Red/Green/Refactor For Rover Facing South

Once again, we can write a failing test for when the Rover faces South

[Test]
public void AndFacingSouthThenYIncreasesByOne()
{
  var rover = new Rover { Orientation = Direction.South };
  var initialLocation = rover.Location;

  rover.MoveBackward();

  var expectedLocation = new Coordinate(initialLocation.X, initialLocation.Y + 1);
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.South, rover.Orientation);
}

And now enough code to make it pass

1
2
3
4
5
6
7
public void MoveBackward()
{
  if (Orientation == Direction.North) {
    Location=Location.AdjustYBy(-1);
  }
  Location = Location.AdjustYBy(1);
}

Once again, now that we have passing tests, is there anything we want to refactor? The business rules look to be simple enough so I don’t feel the need to refactor those. When we look at the test code, the test seems straightforward and I’m not sure what I’d simplify.

Time to commit these changes and on to the next requirement.

Red/Green/Refactor For Rover Facing East

Third verse same as the first, we can write a failing test for when the Rover faces East

[Test]
public void AndFacingEastThenXDecreasesByOne()
{
  var rover = new Rover { Orientation = Direction.East };
  var initialLocation = rover.Location;

  rover.MoveBackward();

  var expectedLocation = new Coordinate(initialLocation.X - 1, initialLocation.Y);
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.East, rover.Orientation);
}

And now enough code to make it pass

public void MoveBackward()
{
  if (Orientation == Direction.North) {
    Location=Location.AdjustYBy(-1);
  }
  if (Orientation == Direction.South) {
    Location = Location.AdjustYBy(1);
  }
  Location = Location.AdjustXBy(-1);
}

With everything passing again, we can pause to rethink about refactoring but so far so good from my perspective. There are maybe some superficial changes that could be made, but I can’t make a strong enough argument to implement them.

Time to commit and tackle the last requirement!

Red/Green/Refactor for Rover Facing West

Let’s go ahead and write the final test for when the Rover faces West

[Test]
public void AndFacingWestThenXIncreasesByOne()
{
  var rover = new Rover { Orientation = Direction.West };
  var initialLocation = rover.Location;

  rover.MoveBackward();

  var expectedLocation = new Coordinate(initialLocation.X + 1, initialLocation.Y);
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.West, rover.Orientation);
}

And now enough code to make it pass

public void MoveBackward()
{
  if (Orientation == Direction.North) {
    Location=Location.AdjustYBy(-1);
  }
  if (Orientation == Direction.South) {
    Location = Location.AdjustYBy(1);
  }
  if (Orientation == Direction.East) {
    Location = Location.AdjustXBy(-1);
  }
  if (Orientation == Direction.West) {
    Location = Location.AdjustXBy(1);
  }
}

How Does Rover Look?

With the final test in place, let’s take a look at Rover looks like!

public class Rover
{
  public Direction Orientation {get; set;}
  public Coordinate Location {get; set;}

  public Rover()
  {
    Orientation = Direction.North;
    Location = new Coordinate(){X=0, Y=0};
  }

  public void MoveForward()
  {
    if (Orientation == Direction.North) {
      Location = Location.AdjustYBy(1);
    }
    if (Orientation == Direction.South) {
      Location = Location.AdjustYBy(-1);
    }
    if (Direciton == Direction.East) {
      Location = Location.AdjustXBy(1);
    }
    if (Orientation == Direction.West) {
      Location = Location.AdjustXBy(-1);
    }
  }

  public void MoveBackward()
  {
    if (Orientation == Direction.North) {
      Location=Location.AdjustYBy(-1);
    }
    if (Orientation == Direction.South) {
      Location = Location.AdjustYBy(1);
    }
    if (Orientation == Direction.East) {
      Location = Location.AdjustXBy(-1);
    }
    if (Orientation == Direction.West) {
      Location = Location.AdjustXBy(1);
    }
  }
}

Overall, Rover is looking to be in a good spot, however, one thing that stands out is that MoveForward and MoveBackward look similar due to their matching if statements. When I see duplication like this, I start thinking about how possible refactoring techniques to reduce the duplication. However, it can be tough to see a pattern before it establishes.

When to Refactor

When it comes to refactoring to a pattern, I like to have three or more examples so I have a better idea of what use cases need to be supported. A common mistake I see developers make is that they start implementing a pattern before really understanding the problem. My approach is to refactor to a pattern when it makes sense and not before.

Circling back to this duplicated ifs, I’ve got a hunch that TurnLeft and TurnRight will follow a similar approach but I’m curious to know what they’re going to look like and I don’t want to refactor too early. So taking my own advice, I’m going to go ahead and skip refactoring MoveForward and MoveBackward until I at implement TurnLeft as that may change my approach.

Wrapping Up

Just like that, we now have a Rover that knows how to both MoveForward and MoveBackward! Like before, we first started by examining the requirements and coming up with our various test cases. From there, we were able to drive out the functionality by using red-green-refactor and building our software with tests. In the next post, we’ll take a look at implementing the logic for turning left!

Mars Rover – Implementing Rover : Moving Forward

Welcome to the fifth installment of Learning Through Example – Mars Rover! In this post, we’ll pick up from where we left on with Rover and start digging into how to make it move forward! We’ll first examine what it means for the Rover to move forward by looking over the requirements in deeper detail. Once we have a better understanding, we’ll start driving out the functionality by focusing on a simple case and building more complexity. By the end of this post, we’ll have a Rover that will know how to move forward when facing any Direction!

So What Does It Mean To Move Forward?

If we look back at the original requirements for moving forward, we find this single line with regards to moving forward

When the rover is told to move forward, then it will move one rover unit in the direction it’s facing

Super helpful, right? I don’t know about you, but this not nearly enough information for us to start our work because I’m not sure what that actually means!

In this case, we will have a more in-depth conversation with our Subject Matter Expert and we’ll find out that depending on the Orientation of the Rover the Rover‘s Location will change. Through additional conversations, we end up figuring out some more concrete business rules for when the Rover moves forward.

  • Given the Rover is facing North, when it moves forward, then its Y value increases by 1
  • Given the Rover is facing South, when it moves forward, then its Y value decreases by 1
  • Given the Rover is facing East, when it moves forward, then its X value increases by 1
  • Given the Rover is facing East, when it moves forward, then its X value decreases by 1

Great, we have enough information to get started so we can start demonstrating the software and get quicker feedback!

Writing The First Test

Let’s begin writing our first test for the Rover moving forward! We’ll be leveraging the same naming guidelines mentioned in part three to help make the use cases standout in our tests

[Test]
public void AndFacingNorthThenYIncreasesByOne()
{
  // Arrange
  var rover = new Rover() { Orientation = Direction.North};

  // Act
  rover.MoveForward();

  // Assert
  Assert.AreEqual(1, rover.Coordinate.Y);
}

So far, so good! The test matches the intent behind the name and a new developer can see that we’ve created a Rover facing North, called its MoveForward method and making sure that the Y property is 1.

If we try running the test, it will fail because Rover doesn’t have a MoveForward method, so let’s go ahead and write a simple implementation.

1
2
3
public void MoveForward()
{
}

Even though there’s no business logic implemented, it’s enough code for our test to compile and if we run the test, the test fails because Y is not 1.

With that in mind, let’s go ahead and write the simplest thing that could work just to check our approach.

1
2
3
4
public void MoveForward()
{
  Location.Y+=1;
}

Hmm, when we try to compile this code though, we get the following error

Compiler error when trying to update the Location's Y property)

Compiler error when trying to update the Location's Y Property

What’s going on here?

When Things Go Off Track

The error is caused due to the interaction of struct and the Location property implementation. If you recall, structs are value types which means when you assign them to a variable, the variable has its own copy of the struct, not the reference.

// Let's create a location
var location = new Coordinate {X = 0, Y = 0};

// And now let's have newLocation have the same value, NOTE: This isn't a reference to location!
var newLocation = location;

// And if we check if both are equal, they are!
Console.WriteLine(location.Equals(newLocation)); // True

// Now let's change location's X value
location.X = 200;

// That works just fine, but if we check what newLocation is, we see that it's stil (0, 0)
Console.WriteLine($"newLocation = ({newLocation.X}, {newLocation.Y})");

// Which means when we compare, they're not the same!
Console.WriteLine(location.Equals(newLocation)); // False

So what does that have to do with the Location property? Well, we’ve defined it as an auto-property which is syntactic sugar for telling the compiler to generate a backing field for the property and to implement default get and set logic.

// Given this definition of Rover
public class Rover
{
  public Coordinate Location {get; set}
}

// This is syntactic sugar for the following
public class Rover
{
  private Coordinate _location;
  public Coordinate Location
  {
    get => _location;
    set => _location = value;
  }
}

So the problem arises from how get is working. It’s returning the backing field which is going to be stored as a variable for use. Recall from above that when we do that type of assignment, we’re working on a copy of the value. So if we try to make changes to the copy, the changes won’t make it back to the backing field which in turn won’t ever update the Location property!

Getting Back On Track

So good job on the compiler letting us know that there’s a problem even if the message is a bit obscure! But how do we fix the problem? Well, to get the code to compile, instead of updating the Y property of Location, let’s go ahead and update the entire Location property instead.

1
2
3
4
public void MoveForward()
{
  Location = new Coordinate { X = Location.X, Y = Location.Y + 1 };
}

And if we try to run our test, we find that it now passes, hooray!

Refactoring The Code

For those keeping track at home, we’re doing a pretty good job of following Test Drive Development (TDD) principles in that we first wrote a failing test, then wrote enough code to make it pass. The third step is to refactor our code (both production and test) to make it easier to work with or to make it more robust.

If we take a look at the MoveForward method, it’s pretty simple and there’s not much we can refactor there for now.

1
2
3
4
public void MoveForward()
{
  Location = new Coordinate { X = Location.X, Y = Location.Y + 1 };
}

Is Our State Correct?

So if our business code is pretty good, let’s take a look at our test and see what can be done.

[Test]
public void AndFacingNorthThenYIncreasesByOne()
{
  // Arrange
  var rover = new Rover() { Orientation = Direction.North};

  // Act
  rover.MoveForward();

  // Assert
  Assert.AreEqual(1, rover.Coordinate.Y);
}

Looking at this code, one thing that stands out is that we’re checking that Y got updated, but we’re not verifying that X nor the Orientation didn’t change. In fact, if we change the implementation of MoveForward to set the X value to be 200 and change the Orientation to be South, the test would still pass and it clearly shouldn’t!

Thankfully, we can mediate this oversight by creating an expectedLocation which will have the expected X and Y values for the Rover. In addition, we’ll update one Assert to use this new value and add another Assert to verify the Orientation

[Test]
public void AndFacingNorthThenYIncreasesByOne()
{
  // Arrange
  var rover = new Rover {Orientation = Direction.North};

  // Act
  rover.MoveForward();

  // Assert
  var expectedLocation = new Coordinate { X = 0, Y = 1};
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.North, rover.Orientation);
}

Nice! We’re now much more explicit about our expectations of Rover should be at this exact Location and should have this exact Orientation, otherwise, fail the test.

Are There Hidden Assumptions?

While looking at this test, there’s one more subtle issue with code, can you spot it?

We’re making an assumption about what the initial Location for the Rover! What if the Rover started off at (5, 5) instead of (0, 0)? This test would fail, but not for the right reason (an error in the production code), but due to fragility in the way the test was written.

If we wanted to harden this test, we have two approaches

Setting the Location

We could change our Arrange step to explicitly set the initial location of Rover to be (0, 0). This would guarantee the initial setup and if the default Location were to ever change, our test would still pass.

[Test]
public void AndFacingNorthThenYIncreasesByOne()
{
  // Arrange
  var rover = new Rover
  {
    Orientation = Direction.North,
    Location = new Coordinate {X = 0, Y = 0},
  };

  // Act
  rover.MoveForward();

  // Assert
  var expectedLocation = new Coordinate { X = 0, Y = 1};
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.North, rover.Orientation);
}

Capturing the Initial Location

When we look at this test and the code we’re testing, the key thing that we’re wanting to test is that the right value was modified correctly (in this case either by +1 or -1). Given that, we could update our Arrange step to capture what the initial Location was and then update our Assert step to know about the location.

[Test]
public void AndFacingNorthThenYIncreasesByOne()
{
  // Arrange
  var rover = new Rover {Orientation = Direction.North};
  var initialLocation = rover.Location; // capturing the initial location

  // Act
  rover.MoveForward();

  // Assert
  var expectedLocation = new Coordinate { X = initialLocation.X, Y = initialLocation.Y+1};
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.North, rover.Orientation);
}

Given the two approaches, I like the idea of capturing the initial location, so that’s what I’m going to go with.

Writing Additional Tests

Now that we have a passing test for Rover and moving forward, let’s go ahead and implement another piece of functionality by writing a test for when the Rover is facing South

Red/Green/Refactor for Rover Facing South

[Test]
public void AndFacingSouthThenYDecreasesByOne()
{
  // Arrange
  var rover = new Rover {Orientation = Direction.South};
  var initialLocation = rover.Location; // capturing the inital location

  // Act
  rover.MoveForward();

  // Assert
  var expectedLocation = new Coordinate { X = initialLocation.X, Y = initialLocation.Y-1};
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.South, rover.Orientation);
}

And write enough code to make it pass!

1
2
3
4
5
6
7
8
9
public void MoveForward()
{
  if (Orientation == Direction.North) {
    Location = new Coordinate { X = Location.X, Y = Location.Y + 1 };
  }
  else {
    Location = new Coordinate { X = Location.X, Y = Location.Y - 1};
  }
}

Now that we have a passing test suite again, is there anything we want to refactor? Are there any patterns starting to emerge?

From the business code, MoveForward seems pretty straightforward and I’m not sure what refactor I could do there that would make a lot of sense right now.

If we take a look at the test code, I’m noticing that our two tests so far look almost like carbon copies of each other. In fact, if we take a closer look, it seems like the only differences between the two tests are the Rover‘s Orientation and the expectedLocation. I’m really tempted to refactor this code to be a bit more DRY and remove some duplication. However, I’ve only seen two examples so far and before I refactor to a pattern, I actually want the pattern to manifest first so I know what the pattern is.

Let’s keep writing some more tests and see what pattern emerges!

Red/Green/Refactor for Rover Facing East

Now that the Rover can move forward when facing North or South, let’s go ahead and write a test for when the Rover faces East.

  [Test]
  public void AndFacingEastThenXIncreasesByOne()
  {
    // Arrange
    var rover = new Rover {Orientation = Direction.East};
    var initialLocation = rover.Location;

    // Act
    rover.MoveForward();

    // Assert
    var expectedLocation = new Coordinate { X = initialLocation.X+1, Y = initialLocation.Y};
    Assert.AreEqual(expectedLocation, rover.Location);
    Assert.AreEqual(Direction.East, rover.Orientation);
  }

With the test in place, let’s write enough code to make it pass.

public void MoveForward()
{
  if (Orientation == Direction.North) {
    Location = new Coordinate { X = Location.X, Y = Location.Y + 1 };
  }
  if (Orientation == Direction.South) {
    Location = new Coordinate { X = Location.X, Y = Location.Y - 1};
  }
  if (Direction == Direction.East) {
    Location = new Coordinate {X = Location.X + 1, Y = Location.Y};
  }
}

With this passing test, let’s take a look at possible refactoring opportunities.

Refactoring Coordinate

If we look at the production code, I’m getting really tired of having to write Location = new Coordinate {X=Location.X..., Y=Location.Y...} because I know I’m going to have to write this similar logic for the last remaining test for moving forward and probably something similar for moving backward.

Looking at the way we’ve been modifying Coordinate, it seems like we’re every modifying X or Y by a set amount, so what if we wrote some methods that could adjust either X or Y?

If we take a look at Coordinate, it seems like we have a struct with the two properties in mention, so let’s add a method called AdjustXBy that will return a new Coordinate with X adjusted by that value and keep Y the same

1
2
3
4
5
6
7
8
9
public struct Coordinate
{
  public int X {get; set;}
  public int Y {get; set;}

  public Coordinate AdjustXBy(int adjustment)
  {
    return new Coordinate {X = X+adjustment, Y=Y};
  }

With this change in place, let’s go ahead and update our MoveForward method to use this new code!

public void MoveForward()
{
  if (Orientation == Direction.North) {
    Location = new Coordinate { X = Location.X, Y = Location.Y + 1 };
  }
  if (Orientation == Direction.South) {
    Location = new Coordinate { X = Location.X, Y = Location.Y - 1};
  }
  if (Direction == Direction.East) {
    Location = Location.AdjustXBy(1);
  }
}

Even in this small example, this addition is already more concise of our intent than the other two cases. After doing a quick verification that the test still passes (otherwise the refactor isn’t a refactor), let’s go ahead and add a new method to Coordinate called AdjustYBy that is similar to AdjustXBy

1
2
3
4
  public Coordinate AdjustYBy(int adjustment)
  {
    return new Coordinate {X=X, Y=Y+adjustment};
  }

And let’s go ahead and update MoveForward to take advantage of this new functionality

public void MoveForward()
{
  if (Orientation == Direction.North) {
    Location = Location.AdjustYBy(1);
  }
  if (Orientation == Direction.South) {
    Location = Location.AdjustYBy(-1);
  }
  if (Direciton == Direction.East) {
    Location = Location.AdjustXBy(1);
  }
}

After making that much change to the production code, we’ll go ahead and run our test suite again and it seems like the change is working as expected, nice!

Refactoring Test Code

Now that we’ve refactored the business rules and our test suite is passing correctly, we can take a look at refactoring our test code. With the addition of the East test, the tests are definitely following a pattern and I should be able to extract out that logic to a single test and then pass in different parameters (even though the link is to NUnit, most test frameworks support this concept).

  [Test]
  public void AndFacingNorthThenYIncreasesByOne()
  {
    // Arrange
    var rover = new Rover {Orientation = Direction.North};
    var initialLocation = rover.Location; // capturing the inital location

    // Act
    rover.MoveForward();

    // Assert
    var expectedLocation = new Coordinate { X = initialLocation.X, Y = initialLocation.Y+1};
    Assert.AreEqual(expectedLocation, rover.Location);
    Assert.AreEqual(Direction.North, rover.Orientation);
  }
[Test]
public void AndFacingSouthThenYDecreasesByOne()
{
  // Arrange
  var rover = new Rover {Orientation = Direction.South};
  var initialLocation = rover.Location; // capturing the inital location

  // Act
  rover.MoveForward();

  // Assert
  var expectedLocation = new Coordinate { X = initialLocation.X, Y = initialLocation.Y-1};
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.South, rover.Orientation);
}
[Test]
public void AndFacingEastThenXIncreasesByOne()
{
  // Arrange
  var rover = new Rover {Orientation = Direction.East};
  var initialLocation = rover.Location;

  // Act
  rover.MoveForward();

  // Assert
  var expectedLocation = new Coordinate { X = initialLocation.X+1, Y = initialLocation.Y};
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.East, rover.Orientation);
}

Given the differences between the tests, we would need to extract the starting Direction and the expectedLocation to be parameters. However, the expectedLocation is based on the initialLocation which is currently based on whatever the Rover defaults to.

Based on that chain, if we wanted to do this refactor, we would have to pass in a Rover as the parameter and I really don’t like that idea because if Rover grows to be bigger, then creating a Rover becomes more involved and I don’t want to inflict that onto my test. In addition, one thing that is nice about our tests is that they’re easy to read and to follow their logic which has a ton of value given that developers spend more time reading code than writing code.

All of that to say, that even though the tests look similar, I’m going to pass on refactoring to a single unified test because I’d be trading readability for removing duplication and these tests are small enough that I don’t think it’s that much technical debt to take on.

Red/Green/Refactor for Rover Facing West

With the latest test, we’re 3/4 of the way through implementing MoveForward, so let’s go ahead and write another failing test for when the Rover faces West.

[Test]
public void AndFacingWestThenXDecreasesByOne()
{
  // Arrange
  var rover = new Rover {Orientation = Direction.West};
  var initialLocation = rover.Location;

  // Act
  rover.MoveForward();

  // Assert
  var expectedLocation = new Coordinate { X = initialLocation.X-1, Y = initialLocation.Y};
  Assert.AreEqual(expectedLocation, rover.Location);
  Assert.AreEqual(Direction.West, rover.Orientation);
}

With the test in places, let’s write enough code to make the test pass by taking advantage of Coordinate.AdjustXBy

public void MoveForward()
{
  if (Orientation == Direction.North) {
    Location = Location.AdjustYBy(1);
  }
  if (Orientation == Direction.South) {
    Location = Location.AdjustYBy(-1);
  }
  if (Direciton == Direction.East) {
    Location = Location.AdjustXBy(1);
  }
  if (Orientation == Direction.West) {
    Location = Location.AdjustXBy(-1);
  }
}

And with this latest addition, not only do we have a passing test suite, but we’ve also covered the business rules for when the Rover moves forward, completing this part of the kata, nice!

As a recap, here’s what Rover and WhenMovingForward looks like

public class Rover
{
  public Direction Orientation {get; set;}
  public Coordinate Location {get; set;}

  public Rover()
  {
    Orientation = Direction.North;
    Location = new Coordinate(){X=0, Y=0};
  }

  public void MoveForward()
  {
    if (Orientation == Direction.North) {
      Location = Location.AdjustYBy(1);
    }
    if (Orientation == Direction.South) {
      Location = Location.AdjustYBy(-1);
    }
    if (Direciton == Direction.East) {
      Location = Location.AdjustXBy(1);
    }
    if (Orientation == Direction.West) {
      Location = Location.AdjustXBy(-1);
    }
  }
}
[TestFixture]
public class WhenMovingForward()
{
  [Test]
  public void AndFacingNorthThenYIncreasesByOne()
  {
    // Arrange
    var rover = new Rover {Orientation = Direction.North};
    var initialLocation = rover.Location;

    // Act
    rover.MoveForward();

    // Assert
    var expectedLocation = new Coordinate { X = initialLocation.X, Y = initialLocation.Y+1};
    Assert.AreEqual(expectedLocation, rover.Location);
    Assert.AreEqual(Direction.North, rover.Orientation);
  }

  [Test]
  public void AndFacingSouthThenYDecreasesByOne()
  {
    // Arrange
    var rover = new Rover {Orientation = Direction.South};
    var initialLocation = rover.Location;

    // Act
    rover.MoveForward();

    // Assert
    var expectedLocation = new Coordinate { X = initialLocation.X, Y = initialLocation.Y-1};
    Assert.AreEqual(expectedLocation, rover.Location);
    Assert.AreEqual(Direction.South, rover.Orientation);
  }

  [Test]
  public void AndFacingEastThenXIncreasesByOne()
  {
    // Arrange
    var rover = new Rover {Orientation = Direction.East};
    var initialLocation = rover.Location;

    // Act
    rover.MoveForward();

    // Assert
    var expectedLocation = new Coordinate { X = initialLocation.X+1, Y = initialLocation.Y};
    Assert.AreEqual(expectedLocation, rover.Location);
    Assert.AreEqual(Direction.East, rover.Orientation);
  }


  [Test]
  public void AndFacingWestThenXDecreasesByOne()
  {
    // Arrange
    var rover = new Rover {Orientation = Direction.West};
    var initialLocation = rover.Location;

    // Act
    rover.MoveForward();

    // Assert
    var expectedLocation = new Coordinate { X = initialLocation.X-1, Y = initialLocation.Y};
    Assert.AreEqual(expectedLocation, rover.Location);
    Assert.AreEqual(Direction.West, rover.Orientation);
  }
}

Wrapping Up

With this final test in place, we have the core functionality for when the Rover moves forward. In addition, we’ve written enough tests and functionality now that if requirements were to change, we have a pretty good guess on what the work involved would be. In the next part of the kata, we’ll start implementing a new piece of functionality!

Mars Rover – Implementing Rover : Creation

In this installment of the Mars Rover kata, we’re going to start implementing the Rover type! First, we’re going to review the models that we derived in Part 2 of the series. From there, we’ll take a look at the various requirements and see which piece to start implementing. From there, we’ll write our first tests, driving the new functionality and running into a snag or two on the way. By the end of this post, we’ll be one requirement down for the kata and have a better understanding of the Rover type!

Model Review

As a recap, we’ve derived the following models and implementations for the kata so far. For this post, we’re going to be spending the majority of our time working with Rover, but it’s good to know what our base looks like.

1
2
3
4
5
6
public enum Command
{
  MoveForward, MoveBackward,
  TurnLeft, TurnRight,
  Quit
}
1
2
3
4
public enum Direction
{
  North, South, East, West
}
1
2
3
4
5
public class Coordinate
{
  public int X {get; set;}
  public int Y {get; set;}
}
1
2
3
4
5
public class Rover
{
  public Direction Orientation {get; set;}
  public Coordinate Location {get; set;}
}

With this in mind, let’s start working on adding some new functionality to Rover!

In the beginning

Picking an Approach

When I begin a new feature, one thing that I’m always thinking about is how do I break down the work ahead of me in such a way that I can start delivering value much faster. This doesn’t mean that I’m skipping on quality, but it does mean that I value quicker feedback than 100% test coverage. With that being said, when I look at the requirements, something that I can implement pretty quickly is that when the Rover starts, it should be at (0, 0) facing North.

Like everything in development, there are multiple ways we could implement this functionality

  1. Provide a default constructor for Rover that sets those values explicitly
  2. Update the Program.cs file to set those values for Rover

With the first approach, we’re encoding this business rule into the Rover type and forcing that when anyone creates an instance of Rover, it will always be at (0, 0) facing North which is a nice way of putting the business rule into the right component.

With the second approach, we’re going to let Rover be a dumb component and have some other component decide these values. The downside to this approach is that if there’s an error with Rover, we won’t know if it’s because of how Rover is behaving or how it was created.

Both approaches are valid, so I’m just going to pick one and use that for now. If later down the road we need to make a change, we’ll update as needed. With that being said, I’m leaning towards the first approach, so let’s go ahead and write our first unit test on making sure that Rover is facing North.

Gaining Our Bearings

Creating a unit test for Rover where the Orientation should be North would look like the following:

1
2
3
4
5
6
7
8
9
[Test]
public void ThenTheRoverIsFacingNorth()
{
  // Arrange and Act
  var rover = new Rover();

  // Assert
  Assert.AreEqual(Direction.North, rover.Orientation);
}

What’s interesting here is that when we run this test, it passes! But why does it pass? If we look back at Rover, here’s how we’ve defined it.

1
2
3
4
5
public class Rover
{
  public Direction Orientation {get; set;}
  public Coordinate Location {get; set;}
}

We don’t have any constructors defined nor are we setting values for the two properties, so how does it know that Orientation is North?

Magic?

Not quite! Since we’re not setting any value for Orientation, it will be whatever the default value for Direction is. We can determine that by using the default operator in C#.

default(Direction); // North

But why is North the default value for Direction?

If you remember how Direction was defined, we decided to define it as an enum which is really a number in disguise.

1
2
3
4
public enum Direction
{
  North, South, East, West
}

So what’s the default value for a number?

default(int); // 0

So what’s happening here is that when the Rover is created, it looks for any logic in the default constructor. Since there isn’t one, it will set default values to both properties. For Orientation, that value will be 0 and by default, it will be the first value listed in the Direction enum.

So for fun, if we change North to be the second choice in Direction

1
2
3
4
public Direction
{
  South, North, East, West
}

then our test fails with our expected error.

Failing unit test for Mars Rover

Failing unit test for Mars Rover as the expected value was North, but the value was South.

I don’t know about you, but someone changing the ordering of an enum shouldn’t be causing a failing test. Luckily, resolving this issue is as simple as explicitly stating the Orientation in Rover‘s default constructor.

public class Rover
{
  public Direction Orientation {get; set;}
  public Coordinate Location {get; set;}

  public Rover()
  {
    Orientation = Direction.North;
  }
}

Now, our test passes even if we change the ordering of values in Direction!

Getting to Ground Zero

Now that we have our Orientation figured out, let’s go ahead and write our next test to make sure that the Location is correct.

1
2
3
4
5
6
7
8
9
[Test]
public void ThenTheRoverIsAt00()
{
  // Arrange and Act
  var rover = new Rover();

  var expectedLocation = new Coordinate{X=0, Y=0};
  Assert.AreEqual(expectedLocation, rover.Location);
}

When we run the test, we get the following error:

Failing coordinate test for rover

Failed test for Rover since it was expecting a Coordinate, but the Location was null.

Ah, yeah, that makes sense, Location is a Coordinate which is an object. Because we’ve not explicitly set Location, the default value is null.

default(Coordinate); // null

So let’s go ahead and update the Rover constructor

public class Rover
{
  public Direction Orientation {get; set;}
  public Coordinate Location {get; set;}

  public Rover()
  {
    Orientation = Direction.North;
    Location = new Coordinate(){X=0, Y=0};
  }
}
````

So lets re-run our test now.

```csharp
[Test]
public void ThenTheRoverIsAt00()
{
  // Arrange and Act
  var rover = new Rover();

  var expectedLocation = new Coordinate{X=0, Y=0};
  Assert.AreEqual(expectedLocation, rover.Location);
}

Failing test for coordinate since they're different

Failed unit test since the expected coordinate is not the same as the actual coordinate..

What’s going on here?

Not All Locations Are Created Equally

When we leverage Assert.AreEqual, under the hood, it’s leveraging the built-in Equals method for the values being passed in. For primitive types, this will do a comparison by values, but if we’re comparing objects, then it will do comparison by reference.

Given this, the problem we’re running into now is that even though I have two Coordinates that have the same value, since they are two different objects, then Assert.AreEqual will fail. We’ve got a couple of different ways to solve this problem.

Overriding the Equals Method

One way to solve the problem is by overriding the Equals method on the Coordinate class and override the logic so that two Coordinates are the same if all of their properties are the same. This is a pretty solid approach to take if that’s how equality should work everywhere. With that being said, here are some things to keep in mind when using this technique.

First, if the class gains a new property, you will need to remember to update the Equals method, otherwise, you’ll get interesting behavior when two objects that have a single difference are being treated as the same.

Next, if you override Equals, then you must override GetHashCode as well. If you fail to do this, this will generate a warning during compilation time, but the bigger problem is that for two objects that are the same based on the definition of Equals but hash differently, then you will fail to find the item correctly in Dictionary and HashSet structures. When implementing GetHashCode, you should use the same properties for hashing as you would for equality checking.

Overall, I will use this approach if equality for this type needs to be by value for everywhere in the application but this isn’t my favorite approach because developers need to remember to update both Equals and GetHashCode when new properties are added.

Going from class to struct

The second approach we can take to have equality by value is by changing our type for Coordinate from a class to a struct. The cool thing about structs is that they by default handle equality by value so if you do need to add another property, things will work as expected. However, like all things in software, there are a couple of things to be aware of.

First, structs have to have an empty, default constructor. So if there are some validation rules that need to be checked in the constructor, there’s no way to force that to happen. Another effect of this drawback is that if a struct needs two things to exist or it shouldn’t be created, you can’t force callers to pass them in which can play havoc with making illegal states unrepresentable.

Second, when working with structs, and you change one of the properties, you’re modifying a copy of the struct and not the original version.

public struct Date
{
  public int Month {get; set;}
  public int Day {get; set;}
  public int Year {get; set;}
}

public class Order
{
  public Date TransactionDate {get; set;}
  public decimal Total {get; set;}
}

var order = new Order{Total=9.99m}; // at this point, Order will have a Date of 0/00/0000
order.TransactionDate = new Date{Month=6, Day=22, Year=2020}; // Order has a new date!
order.TransactionDate.Day = 23 // Fail to compile => Cannot modify the return value of `TransactionDate`
                              // because it is not a variable

It’s not a problem if you replace the whole struct value, but if you only want to change a part of it, you’ll need to replace the whole value. If you’ve ever worked with the DateTime type, you’ll notice that you can’t change values, but you can create a new DateTime value with updated properties.

Overall, I prefer this approach when modeling values where dependencies aren’t required and there’s not a need for validation logic.

Creating an Equals Method for Testing

The third approach you can take when checking if things are equal is by defining a custom Equals method that lives in my test suite and is only used for testing. The benefit of this approach is that I can now verify my objects by value, without the need of overriding two different methods. In addition, since this type of equality is only needed for testing, I can now have this logic live in the test project. In addition, I can still have validation logic and non-default constructors for my classes.

The main downside to this approach is that if there’s a new property added to the class that you’ve written custom equality logic for, you’ll need to update this method as well.

Overall, I prefer this approach when I need equality by value for testing purposes only and I don’t want to make wide-sweeping changes by changing my type from class to struct.

Making Locations Equal

Now that we have some more information about why the test failed and a few different approaches, let’s take a look at what approach makes sense here. First off, Coordinates really don’t have any validation logic. Second, in our system, if we have two Coordinates with the same values, then they should be considered the same. Given these assumptions, I’m going to go ahead and change the type definition from class to struct.

1
2
3
4
5
public struct Coordinate
{
  public int X {get; set;}
  public int Y {get; set;}
}

Now that we’ve made that change, if we re-run our test, it passes!

Wrapping Up

In this post, we started writing tests on Rover. We started off by adding a default constructor which sets the Orientation to Direction.North and explored about how setting defaults explicitly can protect us from changes in the future. From there, we wrote a test on Location and learned about how Assert.AreEquals leverages default equality and a few different approaches to solving this problem. Now that we have a way to assert against Orientation and Location, we can start writing tests for when the rover moves forward!