There are many things to be considered when it comes to software quality. Surely it needs to be well written (remember, “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” Martin Fowler) so that it’s more easily maintainable, but it will be of no use if it does not meet your client’s requirements. Some questions may arise from these premises, (and that for sure have haunted us at least once whenever we looked at that beautiful screen with lots of code 😁), the main one probably being: How do we achieve it then? Is there a kind of “recipe” we can follow that helps us optimize the development process such that we have these characteristics of a well done software?
Experienced well known developers have come up with ways to tailor this process so that a few goals can be met: Domain-Driven Design, by Eric Evans for instance, aims to lead the development in such a way that the developer focuses on, well… expressing efficiently what the software is all about in its Domain, which is, in turn, completely based on the customer’s businesses. Robert Martin (also known as “Uncle Bob”) also makes a similar statement in his book, Clean Architecture, where he says that your software “should scream its intent” and then proceed on teaching techniques on how to architecturally achieve that, so you can guess it by, ideally, just glancing at its code and the way it’s organized.
But after all, when it’s done, will it work as expected? Will there ever be any hidden odd behaviour that couldn’t be found when the algorithms were first tested with the happy path? Any miscalculations? Any weird exceptions that weren’t expected to happen? Kent Beck’s got you covered!
Test-Driven Development is a book that explains how to write software having the use cases in mind, but at the same time, not leaving behind any other other good practices we should take into account when writing code. In Beck’s words, its goal is to write “Clean Code that works”. It’s divided in three main parts: The Money Example, The xUnit Example, and Patterns for Test-Driven Development. This post will be divided into two parts: in this first one, we’ll be focusing on a small summary of its first part, which gives an introductory idea of how it works, and applies it by writing an application that handles multi-currency scenarios. The second part will bring some comments on how the experience of applying such concepts has been for us.
If you’ve been developing for some time, you may have heard of the “red/green/refactor” mantra. In fact, it’s one of the first things that’s described in the book. It’s described as follows:
- “Red: Write a little test that doesn’t work, and perhaps doesn’t even compile at first
- Green: Make the test work quickly, committing whatever sins necessary in the process
- Refactor: Eliminate all of the duplication created in merely getting the test to work”
Source: https://quii.gitbook.io/learn-go-with-tests/
This is basically the programming style it focuses on most of the time. By examples, he proves that it’s possible to write that way. Additionally, he mentions that they can have a “social effect”, as follows:
- “If the defect density can be reduced enough, then Quality Assurance (QA) can shift from reactive work to proactive work.” This basically means that, the QA team can focus more on whether the application meets the business requirements rather than trying to find some nasty exceptions that appear from time to time
- Additionally, we can always have shippable software with new functionalities, which leads to new business relationships with customers
- If the number of surprises can be reduced enough, then project managers can estimate more accurately and even involve the customer in the development process
- If the technical conversation can be made clear enough, then software engineers “can work in minute-by-minute” collaboration instead of daily or weekly collaboration
So, it looks like we have a plan and a motivation why we should do so. The author proceeds on with the “Money Example”. It basically is an application that should be able to handle the conversion of many existing currencies to US Dollars. For each problem to be solved, there’s a “to do” list that will have its items crossed out as each item is solved. As the “spoiler” already told us, each chapter will have a test written that fails, and slowly makes it work (yes, I meant that word. The approach taken is to write each piece of code tiny step by tiny step. Someone who’s been writing code for a while can even guess what the next steps are. And that’s on purpose. The author says that the goal should be to make the real world development as easy and predictable as this. Of course he also mentions that you don’t need to always write code like this and can take greater steps when you feel more comfortable with TDD).
A small example of how it’s done
You may be asking now: right, got it! But… how would that be and… why, considering that I may not even know what should be written until I start doing so? I’ve never done that. Writing code (a test) that won’t even compile at first seems, uh… odd to me. (Yes, it was actually me when I first studied about it 😁)
Writing the test first forces us to think about how our software should behave and also, makes us think about (and write) a good Application Program Interface – API. Some may think that it must be hard to begin with tests. Beck’s argument about it is that, you shouldn’t even be writing the production code if you don’t know what the requirements are and how it should be done. This would just lead to a code that’s more error prone and with an interface that might be cumbersome to be consumed. It may make sense for you right after you finish writing it but, as soon as another programmer (or even yourself at a later moment) starts consuming it for some different scenarios, they may find out that’s just awkward and, sadly, it can be too late.
A quick note: the example below is based on the book itself (the test is almost the same but the method name, which was written based on the MethodUnderTest_Scenario_ExpectedResult pattern), but there are some slightly different implementations from the one in the book, as this is also my interpretation of how it’s done. Also, the author uses Java to write the examples, while I’ll be using C#. The concepts behind it all are the same though.
Having said that, it starts with its first requirement: the application should support the multiplication among currencies. It starts simple, by just giving the software the ability to multiply equivalent currencies.
And that’s how it begins! No Dollar class, no constructor, no Times method implementation… it doesn’t compile. This is the “red” step.
[Fact]
public void Times_WithPositiveMultiplier_ShouldMultiplyCorrectly()
{
Dollar five = new Dollar(5);
five.Times(2);
assertEqual(10, five.Amount);
}
Now, it goes to the “green” step. It should compile and it should pass, even though the code may be something really silly, written just so that the test passes:
public class Dollar
{
public Amount { get; set; }
public Dollar(int amount) {}
public void Times(int multiplier)
{
Amount = 5 * 2;
}
}
And that’s what it takes. It compiles, and it passes. Of course, the code wouldn’t work in a production environment, as we committed “whatever sins necessary in the process”. Now, we “make it right” (refactor): We had magical numbers, the constructor didn’t really do anything, and others consuming the Dollar
class could set whatever they wanted to the Amount
field.
public class Dollar
{
public Amount { get; private set; }
public Dollar(int amount)
{
Amount = amount
}
public void Times(int multiplier)
{
Amount *= multiplier;
}
}
It should work properly now. The Amount
field has been made read-only for the outside classes and now we do the correct calculation and set it to the Amount
field when the Times
method is invoked. All that while we had a test ensuring no business rule would break.
Value objects
There is more to think about. Could a Dollar
be considered the same as another one as long as their Amount
are the same? Yes, it could. But if you have different instances of objects, they won’t be considered the same, even though their properties have the same values. For doing so, you need to implement a class so that they consider the properties that should be equivalent among the objects for them to be considered the same as another. This pattern is called “Value Object” and it’s important to take it into account. Not implementing it could cause some not expected behaviors, since the object comparison would not work as expected. For achieving it, it must override the Equals
and GetHashCode
methods and, in C#, if the instances of that class are going to be used in collections, you additionally need to make it implement the IEquatable<T>
interface so that the Linq methods (see more here, if you’re interested in this topic) such as Distinct
(which returns another collection with no duplicates) work correctly.
The same approach is taken to consider this new case. I won’t be writing these new TDD driven implementations here so this text doesn’t get (even) bigger than it already is, but you can give it a try if you want some exercise!
Triangulation
Another interesting topic that’s introduced in this book is the concept of Triangulation. The Cambridge Dictionary defines it as “the division of a map or plan into triangles for measurement purposes, or the calculation of positions and distances using this method”. We can draw a parallel and use it when we’re unsure how to refactor or how a given algorithm to solve a problem should be done. You can use this technique to triangulate it. For that, you’ll need two or more examples of what you’re trying to solve. For example, say that you have an instance of Dollar
whose Amount
is 5. From that, you also know that the following are true: five.Times(2)
will have its Amount
as 10, five.Times(3)
will be 15, five.Times(4)
will be 20. With that in mind, you can infer that the rule for the Times
method will be really Amount
times the argument of this method. With that implementation, you wouldn’t be able to just fake the behaviour of the Times
method anymore when writing the test first, but you’d have found out the pattern and, furthermore, the algorithm you’d been looking for.
…
This was an introduction of the TDD’s concept and how we can apply it, along with some interesting topics introduced by this book. In the second part of this post, we’ll be discussing how our experience in applying such concepts has been for us. Definitely make sure to stay tuned so you don’t miss it!
Leave a Reply