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
295: Testing with pytest: Basics
A few years ago, I was working on a pricing engine for a subscription service. I spent about an hour "optimizing" a function that calculated pro-rated refunds. I felt great about the code—it was leaner, faster, and looked elegant. I pushed it to staging, and within ten minutes, the QA lead pinged me. The system was accidentally refunding customers 110% of their payment in specific edge cases. I had spent an hour optimizing the code and four hours manually trying to recreate the bug because I didn't have a repeatable way to verify that my "optimization" hadn't broken the original logic.
That's the moment I stopped viewing testing as a chore and started seeing it as a safety net. In Python, while the built-in unittest module exists, almost everyone I know uses pytest. It's less boilerplate, more intuitive, and frankly, it gets out of your way so you can actually write code.
The Magic of Simple Asserts
The biggest shift you'll notice moving to pytest is that you don't need to wrap everything in classes or learn a dozen different assertion methods like self.assertEqual() or self.assertTrue(). You just use the standard Python assert statement. If the expression following assert is true, the test passes. If it's false, pytest catches the exception and tells you exactly why it failed.
Let's say we have a simple function in a file called shopping_cart.py that calculates a total after applying a discount:
def apply_discount(total, discount_percent):
if not (0 <= discount_percent <= 100):
raise ValueError("Discount must be between 0 and 100")
return total * (1 - discount_percent / 100)
To test this, you just write a function. No fancy setup required. I usually keep my tests in a separate file to keep the production code clean:
# test_shopping_cart.py
from shopping_cart import apply_discount
def test_apply_discount_standard():
assert apply_discount(100, 20) == 80
def test_apply_discount_zero():
assert apply_discount(100, 0) == 100
def test_apply_discount_full():
assert apply_discount(100, 100) == 0
Organizing Your Tests for Discovery
You might be wondering how pytest knows which functions are actually tests and which are just helper functions. It uses a naming convention called "test discovery." By default, pytest looks for files that start with test_ or end with _test.py. Inside those files, it looks for functions that start with test_.
I've seen developers try to name their tests things like check_logic() or verify_discount(). pytest will simply ignore those. Stick to the test_ prefix. It feels repetitive at first, but it saves you from having to manually maintain a list of every test case in your project. As your project grows to hundreds of tests, this automatic discovery is a lifesaver.
Running the Suite and Reading Failures
To run your tests, you don't call the script directly. Instead, you run pytest from your terminal in the root directory of your project. You can just type pytest, and it will scan your folders, find every test file, and execute every test function.
The real power, however, is in the failure reports. When a standard assert fails in a normal script, you just get an AssertionError. When it fails in pytest, the tool performs "assertion introspection." It will show you the exact values of the variables at the moment of failure. If you expected 80 but got 85, it doesn't just say "False"; it shows you the math that led to 85. This eliminates the need to pepper your test code with print() statements just to see what went wrong.
📋 Practical Task
Build a Validator Test Suite for an Inventory System
You have been handed a piece of legacy code that manages warehouse stock. The function validate_stock_withdrawal(current_stock, request_amount) is supposed to return True if the withdrawal is possible and False if the request is invalid or exceeds stock. However, the original developer left some bugs in the logic.
The Source Code (inventory.py):
def validate_stock_withdrawal(current_stock, request_amount):
# Bug: This doesn't handle negative request amounts correctly
if request_amount < 0:
return True
if request_amount <= current_stock:
return True
return False
Your Task:
- Create a file named
test_inventory.py. - Write a test
test_sufficient_stockthat assertsTruewhen stock is 10 and request is 5. - Write a test
test_insufficient_stockthat assertsFalsewhen stock is 10 and request is 15. - Write a test
test_negative_requestthat assertsFalsewhen a negative amount is requested (this test should currently fail, revealing the bug in the source code). - Run
pytestin your terminal and observe the failure report for the negative request test.
There are no comments for now.