In my last article, I debunked some misconceptions around Test Driven Development (TDD), and explained some of the benefits of this framework when compared to other approaches. Now, I’m going to show you the power of TDD with an example: let’s create a Loan App.

The Exercise: Creating a Loan App with TDD

In this exercise, I’ll demonstrate how to apply TDD while developing on the OutSystems platform.

Our product owner came up with some requirements for our loan app:

Product Owner and requirements for creating loan app.  

He was also kind enough to provide me with a helpful spreadsheet and a formula so I can understand all the calculations involved:

Formula and requirements for the loan app. 

Meanwhile, the project manager...

The Product Manager and Developer. 

Scenario 1: Payment Calculation

We’ll be developing using TDD as a framework, where:

TDD framework

  • Red - Write a failing test
  • Green - Make the test pass
  • Blue - Refactor

So let's create an app for the tests, the loan app, and a library for the calculations.

The Loan App Test is a reactive web app and has a single testing module that references the TDD Framework (or the official BDD Framework).

Loan app test 

The Loan App is also a web reactive app and includes a library for the financial calculations.

Loan app test  

Red - Create a Failing Test

We need to calculate the payment amount based on the initial principal, number of payments, and interest rate.

Since this is test-driven, on the test side, we create the initial test.

Loan app TDD 

On the library side, we create a method with a dummy implementation for now.

Creating a method with a dummy implementation

Back to the test side, we implement the test.

Implementing the test

And we have our first failing scenario.

Loan app failing scenario 

Now we can start to code… or can’t we?

We actually already did, driven by the tests. What have we learned so far?

  • We decided where to put the payment method and in what type of module.
  • The payment method needs 3 inputs (Principal, Interest rate, and # of Payments).
  • The payment method has a single output (Payment Amount).
  • The payment method has no dependencies and can live isolated in its own library (yes, I cut some corners here).
  • We learned the domain language, principal, interest rate, number of payments, and payment now all have very specific meanings.

Green - Make the Test Pass

So, now we can flip a switch on our brain and take care of the implementation.

This is a simple mathematical formula. I know how to implement it, so I can just go for the solution.

In some cases, the solution is not clear. If so, we can benefit from coding in microsteps (see Uncle Bob example below) and the solution shows itself on the refactor step.

 Calculating payment

And it fails again, with a rounding issue. Rounding in financial applications is a problem and should be done as late as possible and on a single time so it doesn’t accumulate rounding errors. So, we’ll fix the test.

Calculating payment error

And now we have a passing test.

Calculating payment test passes

Blue - Optimize Your Code

Well, there isn’t much to optimize when you just have a simple formula.

I did create this method as a client-side (Javascript) code. I’m thinking that complex power math operations on a client-side and this won’t be usable by any server-side applications, including traditional web.

Let’s convert this to a service really quickly and do the validations.

On the Financial Utils, copy the client method and paste it on the server-side.

On the tests, replace the client method by the server-side method.

Replacing client method with the server-side method

And with a refactor, we still have a passing test.

Passing test  

Red - Create a Failing Test

We start by coding three more tests.

Tests for loan app 

Developing tests for loan app

And naturally, these will fail.

Failing tests

On the Financial Utils add the validations.

Adding validations to Financial Utils

And now all tests pass.

Test passes

Scenario 2: Payment Schedule

We also need to be able to show the user a payment schedule.

Product Manager and requirements for payment schedule scenario  

Again, we have an example worksheet:

Example worksheet  

Let's get started.

Red - Create a Failing Test

Payment schedule test 

We need another method for this, something that returns the payment schedule. I'm choosing to have the same inputs as the payment method, and output a list with the payment schedule.

Payment schedule

We'll assert for principal amount of payment #10 and the balance for the last payment, which should be zero.

 Payment schedule steps 

And it fails miserably.

Failing test 

Green - Make the Test Pass

Time to flip that switch again on our brain and implement the calculations.

Implementing calculations 

And the test passes.

You can take as many iterations of this step as needed. You don't think I got it right on the very first pass, do you?

Passing test 

Blue - Optimize Your Code

At this moment I can't think of any code optimizations.

Scenario 3: Extra Payments

Often, the user inquires what the payment schedule looks like if they pay something extra.

Product Manager and requirements for extra payments scenario  

Again, we have an example worksheet:

Example worksheet for extra payments scenario

By now, you know the drill.

Red - Create a Failing Test

Whoa, wait a second, just thinking about the methods and the interface, I have many options.

  • Option 1: Create a new method which takes an existing schedule and processes the extra payments.
  • Option 2: Modify the existing method to support the new feature.

Also, should I choose an array of extra payments or force the developer to execute the method as many times as there are extra payments?

Sometimes, as a developer, you’re faced with these types of decisions. There are pros and cons to both of them. So, let's choose option 1 and take microsteps to see where it leads us.

 Extra payment test

Extra payment test steps

And the test fails.

 Failing test  

Green - Make the Test Pass

The simple, straightforward way looks something like this:

Implementing new test

And the test passes.

Test passes  

Blue - Optimize Your Code

Well, this code is almost the same as the Payment Schedule. It needs the interest rate and is re-calculating the payment on every payment turn. Option 2 was a better choice.

Let's refactor this and see if we break all the test cases!

Refactoring extra payment scenario

Replace the payment extra method from the last test:

Replacing payment extra method 

All tests are passing (view from the dashboard).

Tests dashboard

Of course, a lot more tests are needed, but this is it for this example.

Conclusion

TDD is awesome! I’m getting addicted to this rhythm of Red/Green/Blue which on my mind is more like:

  • Method and interfaces
  • Code implementation
  • Refactor/Optimize

Did I write more code when compared to a more traditional “Bug Driven Development”? Yes, for sure. However, being a developer I don’t really mind, after all, it’s what I do.

Think of all the time and effort I saved by not having to do all these tests manually and repeatedly every time I changed something just to check if it was working.

Give TDD and try and check out the components and this example on the Forge:

TDD Forge components 

And if you want to know more about TDD, these will get you started:

This post was inspired by Kent Beck’s book “TDD by Example”.