Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
57: Test-Driven Development in Ruby
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
InsufficientStockErrorrather than allowing the inventory to go negative.
Your workflow:
- Write the test for Requirement 1 $\rightarrow$ Run it (Fail) $\rightarrow$ Implement logic (Pass).
- Write the test for Requirement 2 $\rightarrow$ Run it (Fail) $\rightarrow$ Implement logic (Pass).
- Write the test for Requirement 3 $\rightarrow$ Run it (Fail) $\rightarrow$ Implement logic (Pass).
There are no comments for now.