Testing with pytest

Back

Loading concept...

Testing with pytest: Your Code’s Safety Net 🧪

The Story of the Careful Chef

Imagine you’re a chef in a busy restaurant kitchen. Every dish you make goes to a customer. What if you accidentally added salt instead of sugar to the cake? Disaster!

Smart chefs taste their food before serving. They check if the soup is too salty, if the pasta is cooked right, if the sauce tastes perfect.

pytest is your tasting spoon for code. Before your code goes to real users, pytest checks if everything works correctly. It catches mistakes before anyone else sees them!


1. Writing Test Functions

What Are Test Functions?

Think of test functions like little taste-testers. Each one checks ONE thing about your code.

The Rule: Your test function name must start with test_. That’s how pytest finds it!

# Your actual code (the dish)
def add(a, b):
    return a + b

# Your test (the taste test)
def test_add_two_numbers():
    result = add(2, 3)
    assert result == 5

How It Works

graph TD A["Write your code"] --> B["Write a test function"] B --> C["Test name starts with test_"] C --> D["Run pytest"] D --> E{Pass or Fail?} E -->|Pass| F["Your code works!"] E -->|Fail| G["Fix and try again"]

Simple Example

# calculator.py
def multiply(x, y):
    return x * y

# test_calculator.py
def test_multiply_positive():
    assert multiply(3, 4) == 12

def test_multiply_zero():
    assert multiply(5, 0) == 0

Key Points:

  • One function = One specific check
  • Name tells you what it tests
  • assert is the magic word that checks if something is true

2. Test Classes

Grouping Tests Together

Sometimes you have many tests for the same thing. Like having a whole checklist for one recipe!

Test classes group related tests. The class name must start with Test.

class TestCalculator:

    def test_add(self):
        assert add(1, 1) == 2

    def test_subtract(self):
        assert subtract(5, 3) == 2

    def test_multiply(self):
        assert multiply(2, 4) == 8

Why Use Classes?

graph TD A["TestCalculator Class"] --> B["test_add"] A --> C["test_subtract"] A --> D["test_multiply"] A --> E["test_divide"]

Benefits:

  • Keep related tests together
  • Share setup between tests
  • Organized and easy to find
  • Run all tests in a class at once

3. pytest Assertions

The assert Keyword

assert is like asking a yes/no question. If the answer is “yes” (True), the test passes. If “no” (False), it fails.

# Simple assertions
assert 5 > 3          # Is 5 greater than 3? YES!
assert "cat" == "cat" # Are these equal? YES!
assert 10 != 5        # Are these different? YES!

Common Assertion Patterns

def test_various_assertions():
    # Check equality
    assert 2 + 2 == 4

    # Check if something is in a list
    fruits = ["apple", "banana"]
    assert "apple" in fruits

    # Check if something is True/False
    assert True
    assert not False

    # Check if value is None
    x = None
    assert x is None

pytest Gives Helpful Errors!

When a test fails, pytest shows exactly what went wrong:

assert 3 + 3 == 7
>      6 == 7
E      False

It tells you: “You expected 7, but got 6!”


4. Fixtures

The Prep Kitchen

Before cooking, chefs prepare ingredients: chop vegetables, measure spices, preheat the oven.

Fixtures are your prep work for tests. They set up what your tests need.

import pytest

@pytest.fixture
def sample_list():
    return [1, 2, 3, 4, 5]

def test_list_length(sample_list):
    assert len(sample_list) == 5

def test_list_sum(sample_list):
    assert sum(sample_list) == 15

How Fixtures Work

graph TD A["@pytest.fixture"] --> B["Creates sample_list"] B --> C["Passes to test function"] C --> D["Test uses it"] D --> E["Fixture cleaned up"]

Fixture with Setup and Cleanup

@pytest.fixture
def database_connection():
    # SETUP: Before test runs
    db = connect_to_database()

    yield db  # Give this to the test

    # CLEANUP: After test finishes
    db.close()

def test_save_user(database_connection):
    database_connection.save(user)
    assert True

The yield magic: Everything before yield is setup. Everything after is cleanup!


5. Parametrized Tests

Test Many Values at Once!

What if you want to test adding with 10 different number pairs? Write 10 test functions? No way!

Parametrize lets you run one test with many different inputs.

import pytest

@pytest.mark.parametrize("a, b, expected", [
    (2, 3, 5),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_add(a, b, expected):
    assert add(a, b) == expected

This ONE function runs FOUR times with different values!

Visual: How It Runs

graph TD A["test_add parametrized"] --> B["Run 1: add#40;2,3#41; = 5"] A --> C["Run 2: add#40;0,0#41; = 0"] A --> D["Run 3: add#40;-1,1#41; = 0"] A --> E["Run 4: add#40;100,200#41; = 300"]

More Complex Example

@pytest.mark.parametrize("word, expected_length", [
    ("cat", 3),
    ("hello", 5),
    ("", 0),
    ("Python", 6),
])
def test_word_length(word, expected_length):
    assert len(word) == expected_length

Super Efficient! One test function, many test cases.


6. Marks and Skipping Tests

Special Labels for Tests

Marks are like sticky notes on your tests. They tell pytest something special about each test.

Skip a Test

Sometimes a test isn’t ready yet, or only works on certain computers.

@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
    pass

@pytest.mark.skipif(
    sys.platform == "win32",
    reason="Only runs on Linux"
)
def test_linux_only():
    pass

Mark Tests as Expected to Fail

@pytest.mark.xfail(reason="Known bug #123")
def test_broken_feature():
    assert broken_function() == "works"

xfail = “I expect this to fail.” pytest won’t count it as a failure.

Custom Marks

@pytest.mark.slow
def test_big_calculation():
    # Takes 5 minutes to run
    pass

@pytest.mark.database
def test_save_to_db():
    pass

Run only specific marked tests:

pytest -m slow        # Only slow tests
pytest -m "not slow"  # Everything except slow

Mark Summary

graph TD A["Marks"] --> B["@pytest.mark.skip"] A --> C["@pytest.mark.skipif"] A --> D["@pytest.mark.xfail"] A --> E["@pytest.mark.custom_name"] B --> F["Always skip"] C --> G["Skip if condition true"] D --> H["Expected to fail"] E --> I["Your own categories"]

Running Your Tests

Basic Commands

# Run all tests
pytest

# Run with details
pytest -v

# Run one file
pytest test_calculator.py

# Run one test
pytest test_calculator.py::test_add

See the Results

test_calc.py::test_add PASSED
test_calc.py::test_subtract PASSED
test_calc.py::test_multiply FAILED

=============== 1 failed, 2 passed ===============

Green = Good! Red = Fix it!


Quick Reference

Concept Key Point
Test Functions Start name with test_
Test Classes Start name with Test
Assertions Use assert to check
Fixtures @pytest.fixture for setup
Parametrize Test many values at once
Skip @pytest.mark.skip
Expected Fail @pytest.mark.xfail

Your Journey Forward

You now have a safety net for your code! Every time you write code, write tests too. It’s like having a friend who double-checks your work.

Remember: Good tests = Confident coding. You can change your code without fear because pytest will tell you if something breaks!

đź§Ş Happy Testing!

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.