Skip to Content
Course content

57: Test-Driven Development in Ruby

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to show you something that happens to almost every developer early in their career. We get an exciting feature request, we know exactly how to solve it in our heads, and we dive straight into the implementation. We write the logic, we run the app, it "looks" like it works, and we move on. Then, two weeks later, a bug report hits your desk because of an edge case you didn't consider.

Take a look at this DiscountCalculator. It's simple, right? It takes a total and a discount amount, and returns the final price.

class DiscountCalculator
  def apply_discount(total, discount)
    total - discount
  end
end

If you're like I was, you'd write a quick test to make sure it works for the happy path:

def test_apply_discount
  calc = DiscountCalculator.new
  assert_equal 80, calc.apply_discount(100, 20)
end

The negative price disaster

The test passes. You feel great. But then, a customer uses a $50 gift card on a $30 item. Suddenly, your system is telling the customer that the store owes them $20. You've just created a "negative price" bug because you wrote the code first and the tests as an afterthought.

In a traditional workflow, you'd just add an if statement to the code, run the test again, and call it a day. But that's not Test-Driven Development (TDD). In TDD, we don't fix the code to make the test pass; we write the test to define the behavior we want, and only then do we write the minimum amount of code to satisfy it.

Driving the logic with a failing test

Instead of guessing what else might break, let's use the "Red-Green-Refactor" cycle. First, we write a test for the scenario that currently fails. This is the "Red" phase.

def test_discount_cannot_exceed_total
  calc = DiscountCalculator.new
  # We expect the price to bottom out at 0, not go negative
  assert_equal 0, calc.apply_discount(30, 50)
end

When you run this, it fails spectacularly. This is actually the best part of TDD—the failure tells you exactly what is missing. Now we move to the "Green" phase: writing the simplest possible code to make that specific test pass.

class DiscountCalculator
  def apply_discount(total, discount)
    result = total - discount
    result < 0 ? 0 : result
  end
end

Now both tests pass. We've moved from Red to Green. Finally, we "Refactor." This is where I usually clean up the variable names or simplify the logic, knowing that my tests will catch me if I accidentally break something.

class DiscountCalculator
  def apply_discount(total, discount)
    [0, total - discount].max
  end
end

Why this shift in mindset matters

You might be thinking, "This feels slower. Why write a test for something so obvious?"

It's not about the simple cases; it's about the confidence. When you practice TDD, your tests become a living specification of your application. If you decide to change how discounts work six months from now—maybe adding percentage-based discounts—you don't have to manually click through your UI to make sure you didn't break the flat-rate discounts. You just run your suite. If it's green, you're safe to deploy. If it's red, you caught the bug before the customer did.




📋 Practical Task

Build a TDD-backed Inventory Stock Manager

Your goal is to create an InventoryManager class using a strict TDD approach. Do not write the class logic until you have written a failing test for each of the following requirements:

  • Requirement 1: The manager should be able to add stock to an item. (e.g., adding 10 units to "Apples" should result in 10).
  • Requirement 2: The manager should be able to remove stock. (e.g., removing 3 units from 10 should result in 7).
  • Requirement 3: Removing more stock than is available should raise a custom error InsufficientStockError rather than allowing the inventory to go negative.

Your workflow:

  1. Write the test for Requirement 1 $\rightarrow$ Run it (Fail) $\rightarrow$ Implement logic (Pass).
  2. Write the test for Requirement 2 $\rightarrow$ Run it (Fail) $\rightarrow$ Implement logic (Pass).
  3. Write the test for Requirement 3 $\rightarrow$ Run it (Fail) $\rightarrow$ Implement logic (Pass).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.