We always hear that Test Driven Development (TDD) is a software design technique which is focused on tests. Given that we have to write tests before code, our mindset can easily change to think in terms of testing instead of design. And then we find ourselves thinking about border cases, bad inputs, and stuff that. While this is important for testing, it sidetracks us from the main software development technique, which is design.
In this post I’m going to explain my train of thought when designing software through TDD so you can see and contrast thinking about testing vs. thinking about design. Also, you should easily see how each design decision results in code for testing.
The Exercise
I am tasked to build a money converter. To make things simple, I only need to convert between United States Dollars (USD) and Colombian Pesos (COP).
Selecting a Name for the Component
The first design decision is the name of the component. I’ll call it MoneyConverter
, which denotes its primary functionality. I thought about calling it USD2COPConverter but that was not nearly as clear. I will create this class on the net.velocitypartners.money
package.
Having decided the name and package, I first create a test called MoneyConverterTest
in that package. Then I add a private instance of the class I’m about to create. The Integrated Development Environment (IDE) will complain that it doesn’t exist, so I just tell the IDE to create it for me. The code looks like this so far:
package net.velocitypartners.money; public class MoneyConverterTest { private MoneyConverter converter; }
Thinking About Responsibilities
When designing a class, I like to think in terms of , where you think on the Class Name, the Responsibilities and finally the Collaborators. The responsibility for this class is very simple: convert U.S. dollars to and from Colombian pesos. I also keep in mind the , so that each component only does one thing and does it well. This helps me create components that are small and easy to maintain.
So, having a clear responsibility, let’s think about the . How do I want to call the functionality of this converter and with what parameters? For this, I think of a scenario, converting dollars to pesos, and I write a test with that name. I divide the test in 3 sections, . I like these terms as I first learned tests doing Behavior-Driven development (BDD), but you can use Arrange/Act/Assert or any other template. Then I decide how to call this function and write it in the When section. Let’s say I’ll call it convertUSDtoCOP()
which will receive the amount of dollars to convert. As we are talking about money, I’ll go with a BigDecimal as the data type. I want this example to convert 10 dollars, so this is how my code looks:
@Test public void itConvertsUSDtoCOP(){ //Given BigDecimal amountToConvert = new BigDecimal("10.00"); //When BigDecimal result = converter.convertUSDtoCOP(amountToConvert); }
The IDE will complain again, so I just click and create that method in my class. This is how the actual class looks like so far:
package net.velocitypartners.money; import java.math.BigDecimal; public class MoneyConverter { public BigDecimal convertUSDtoCOP(BigDecimal bigDecimal) { // TODO Auto-generated method stub return null; } }
The Conversion Rate
We still need to decide how the conversion rate will be made available for the class. I could pass it as an additional parameter to the method, but I don’t prefer that approach. I think I’ll go with a constructor argument for this first pass. Let’s add a Before hook to the test that arranges things:
@Before public void setup(){ BigDecimal usdToCopExchangeRate = new BigDecimal("2920.8167"); //Today! converter = new MoneyConverter(usdToCopExchangeRate); }
As it should, the IDE is complaining again. So, I let it create that constructor for me. Done. So this is how the class looks now:
package net.velocitypartners.money; import java.math.BigDecimal; public class MoneyConverter { public MoneyConverter(BigDecimal usdToCopExchangeRate) { // TODO Auto-generated constructor stub } public BigDecimal convertUSDtoCOP(BigDecimal bigDecimal) { // TODO Auto-generated method stub return null; } }
We have all we need to complete a failing test. Given a conversion rate of 2,920.82, I would expect that 10 dollars will get converted to 29,208.20 pesos. Let’s complete the test and run:
@Test public void itConvertsUSDtoCOP(){ //Given BigDecimal amountToConvert = new BigDecimal("10.00"); //When BigDecimal result = converter.convertUSDtoCOP(amountToConvert); //Then assertEquals(new BigDecimal("29208.20"), result); }
Adding an Implementation
Let’s add an implementation to pass the test:
public class MoneyConverter { private static final int SCALE = 2; private BigDecimal usdToCopExchangeRate; public MoneyConverter(BigDecimal usdToCopExchangeRate) { this.usdToCopExchangeRate = usdToCopExchangeRate.setScale(SCALE); } public BigDecimal convertUSDtoCOP(BigDecimal amountInUSD) { return amountInUSD.multiply(usdToCopExchangeRate).setScale(SCALE); } }
Notice I’ve decided to use a scale of 2 for these examples. It’s a design decision, expressed as a test. This may change in the future, but I’m ok starting like this.
Summary and Next Steps
As you can see from the exercise, we have made several design decisions for our class while writing the test. My mind has been focused on the component and how I want it to work, not on testing all possible scenarios looking to make it fail.
This initial design will evolve over time as the application starts demanding more features. While we haven’t taken baby steps as recommended by TDD, we also haven’t rushed to design a generic convert-all-the-currencies object that we may not need at the end. Anyway, even if our app only needs to convert from USD to COP, we know the exchange rate is not going to be as stable as we would like.
So how can I get the updated exchange rate? In the next article, we’ll design that part using TDD and introducing some mocking. Stay tuned!