Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
304: Test-Driven Development Basics in Python
I'll be honest: early in my career, I thought writing tests was a chore that slowed me down. I'd write a whole feature, run it once, see it work for the most obvious case, and call it a day. Then I'd push the code, and an hour later, a bug report would come in because someone entered a negative number or a null value that I hadn't even considered. That's how I learned the hard way that "it seems to work" isn't a strategy.
Let's look at a piece of code that looks perfectly fine at first glance. Imagine we're building a simple discount system for an e-commerce site.
def apply_discount(price, discount_percent):
return price * (1 - discount_percent / 100)
# Testing it manually
print(apply_discount(100, 20)) # 80.0 - Works!
print(apply_discount(50, 10)) # 45.0 - Works!
The Danger of the Happy Path
The code above works for what I call the "happy path"—the scenario where the user does exactly what we expect. But what happens if a marketing manager accidentally sets a discount to 110% during a flash sale? Or what if the price is passed as a negative number by mistake?
print(apply_discount(100, 110)) # -10.0
Suddenly, our store is paying customers to take products. In a real production environment, this is the kind of bug that gets you a very unpleasant phone call at 2:00 AM. We could just add an if statement now to fix it, but that's how we end up with "spaghetti code"—adding patches on top of patches without a clear plan.
Flipping the Script with Red-Green-Refactor
This is where Test-Driven Development (TDD) comes in. Instead of writing the code and then trying to "break" it, we write the test first. We define what "correct" looks like before we even write the logic. TDD follows a rhythmic cycle: Red, Green, Refactor.
- Red: Write a test for a small bit of functionality and watch it fail.
- Green: Write the bare minimum amount of code to make that test pass.
- Refactor: Clean up the code while ensuring the tests still pass.
I prefer using pytest because it's less boilerplate than the built-in unittest module. Let's apply this to our discount bug. First, we write a test that describes the behavior we actually want: the price should never drop below zero.
# test_discount.py
from discount_logic import apply_discount
def test_discount_cannot_be_negative():
# If the discount is 110%, the price should be 0, not -10
assert apply_discount(100, 110) == 0
If we run this right now, it fails. That's our Red phase. Now, we write just enough code in discount_logic.py to make it pass.
Tightening the Logic with pytest
Instead of over-engineering a complex validation system, we'll use max() to ensure we never return a value lower than zero. This is the Green phase.
# discount_logic.py
def apply_discount(price, discount_percent):
final_price = price * (1 - discount_percent / 100)
return max(0, final_price)
Run the test again. It passes. Now, we don't stop there. We might realize that a negative price input is also possible. So, we write another test: test_negative_price_input(). It fails. We update the code to handle it. We repeat this until we've covered all the edge cases we can think of.
The beauty of this approach is that you're not guessing if your fix broke something else. If you change the logic six months from now to add a "maximum discount cap," you just run your suite of tests. If they all stay green, you can sleep soundly knowing you haven't reintroduced that "paying the customer" bug.
📋 Practical Task
Build a TDD-Based Password Strength Validator
Your task is to create a password validator using the TDD process. You must write the tests before the implementation code.
Requirements:
The validator function is_strong_password(password) should return True only if:
- The password is at least 8 characters long.
- It contains at least one digit.
- It contains at least one special character (e.g., !, @, #, $, %).
Instructions:
- Create a file named
test_validator.py. Write three separate tests: one for length, one for digits, and one for special characters. Run them and confirm they all fail (since the function doesn't exist yet). - Create
validator.pyand implementis_strong_password()using the minimum logic required to make those tests pass. - Add a "Happy Path" test case where a password meets all criteria and ensure it returns
True. - Run your tests one last time to ensure everything is green.
There are no comments for now.