DEV Community

Igor Neves Faustino
Igor Neves Faustino

Posted on • Originally published at nfaustino.com

How to start with TDD

When I initially started coding, I felt TDD didn't work because I wouldn't be able to code the test if I didn't know what to do.
After some time has passed, I've come to a different conclusion. When I'm not sure how to handle a problem, I find that creating the test ahead of time helps the most in figuring out how to solve the problem.

What is TDD?

TDD stands for Test-Driven Development and means you put focus on test before the actual production code.

Three TDD laws govern the process you should follow when using TDD. Here they are:

  • You can't write any production code until you have first written a failing unit test.
  • You can't write more of a unit test than is sufficient to fail, and not compiling is failing.
  • You can't write more production code than is sufficient to pass the currently failing unit test.

These laws contain us in a loop where we can only focus on and change one thing at a time. This allows us to take small steps and build up your projects incrementally.

How to start working with TDD

When working with TDD, we always take small steps to reach a greater goal.
To show you how I would've done a code using TDD, we will code the following problem:

I'm assuming you all can create a project and configure jest or whatever test library you like (if you don't know how to do this, I think this is your first step, google can help a lot with this. Don't worry, I'll be waiting here)

Let's start with a simple function that follows the following rules:

  • given a date of entry and exit in a parking lot, return the correct price.
  • each hour costs $1
  • we will always use completed hours to calculate the cost, so if a car is parked for 1:10 hours, we will charge 2 hours

Ok, now that we know the rules, we can start developing our code. Since we are using TDD, the first step is to create a test.

Since we are beginning our code from the test code, is easier to start thinking about the interface that we will be using. In this case, we know that we need the enter and leave hours. So we need to pass this information to the function.

Since we are using typescript, we can use the Date object to pass this info.

So for the first test, we will have something like this:

test("should return the correct price", () => {
  const enterDate = new Date('2022-03-27T10:00:00')
  const leaveDate = new Date('2022-03-27T12:00:00')

  const price = calculateParkingTicket(enterDate, leaveDate)

  expect(price).toBe(2)
})
Enter fullscreen mode Exit fullscreen mode

Now that we have a failing test, we can finally start working on the actual function.

So following the TDD, we are creating the amount of code that is needed to the test pass:

export const calculateParkingTicket = (enter: Date, leave: Date) => {
  return 2
}
Enter fullscreen mode Exit fullscreen mode

Now we have a passing test!!
We have the right amount of code to make your test pass, even though this code doesn't solve our real problem completely.

If the real problem is not solved yet, we will write a new test!!

test("should return the correct price when 5 hours passed", () => {
  const enterDate = new Date('2022-03-27T10:00:00')
  const leaveDate = new Date('2022-03-27T15:00:00')

  const price = calculateParkingTicket(enterDate, leaveDate)

  expect(price).toBe(5)
})
Enter fullscreen mode Exit fullscreen mode

Ok, now we can't only return the number 2 from our function. We need to develop a more complex solution.

const PRICE_PER_HOUR = 1

export const calculateParkingTicket = (enter: Date, leave: Date) => {
  const diferencesInMs = (leave.getTime() - enter.getTime())
  const diferencesInHours = diferencesInMs / (1000 * 60 * 60)
  return diferencesInHours * PRICE_PER_HOUR
}
Enter fullscreen mode Exit fullscreen mode

Looks better! But what will happen if a car stays parked for 2:30 hours?
according to our rules, it should cost $3, so let's write a test for this case

test("should return the correct price 2:30 hours passed", () => {
  const enterDate = new Date('2022-03-27T10:00:00')
  const leaveDate = new Date('2022-03-27T12:30:00')

  const price = calculateParkingTicket(enterDate, leaveDate)

  expect(price).toBe(3)
})
Enter fullscreen mode Exit fullscreen mode

Now that we have a new test we can start to code!

const PRICE_PER_HOUR = 1

export const calculateParkingTicket = (enter: Date, leave: Date) => {
  const diferencesInMs = (leave.getTime() - enter.getTime())
  const diferencesInHours = diferencesInMs / (1000 * 60 * 60)
  const hoursToPay = Math.ceil(diferencesInHours)
  return hoursToPay * PRICE_PER_HOUR
}
Enter fullscreen mode Exit fullscreen mode

So, this concludes our code. Every rule is taken into account and tested.

Now we are free to refactor the code as most as we want, with the certainty that we will not break any rule thanks to the tests!

Conclusion

We coded a basic example with TDD, and I think it demonstrates how to apply this technique correctly.

Working with TDD, in my opinion, has various advantages, such as the ability to take incremental steps, consider how you will interact with the code, and, of course, ensure that everything works correctly.

But, keep in mind that this approach may be hard to implement in the real world. Because not every piece of code is so trivial to test,

Along with TDD, it is critical to have a good architecture that enables the creation of testable code. Using patterns such as dependency injection can also aid in the creation of testable code.

Top comments (0)