Lesson 1 · Pytest deep dive
pytest Tutorial #1: Getting Started — Your First Python Test
Video one of an eighteen-part series on testing real Python code with pytest. Test discovery, plain assert statements, and actually running pytest. Let's…
- CoursePytest deep dive
- Lesson1 of 18
- FormatJupyter notebook · 9 code cells
What you'll learn
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbpytest Deep-Dive, Video 1: Getting Started#
- Video one of an eighteen-part series on testing real Python code with pytest.
- Test discovery, plain assert statements, and actually running pytest.
- Let's get into it.
Part 1: What pytest Is and Why#
import pytest
print(pytest.__version__)
Part 2: A Real Helper - Running pytest Against a String of Code#
import tempfile, os
def run_pytest(code_str, args=None):
with tempfile.NamedTemporaryFile(mode='w', suffix='_test.py', delete=False) as f:
f.write(code_str)
path = f.name
result = pytest.main([path, *(args or [])])
os.unlink(path)
return result
Part 3: The First Real Test Function#
code = '''
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 4: A Real Failing Test, and What pytest Actually Shows#
code = '''
def add(a, b):
return a + b
def test_add_wrong():
assert add(2, 3) == 6
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 5: Multiple Test Functions in One File#
code = '''
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def test_add():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, -1) == -2
def test_subtract():
assert subtract(10, 4) == 6
def test_subtract_wrong():
assert subtract(10, 4) == 5
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 6: Test Discovery Rules#
import tempfile, os, subprocess, sys
def run_pytest_named(code_str, filename, args=None):
tmpdir = tempfile.mkdtemp()
path = os.path.join(tmpdir, filename)
with open(path, 'w') as f:
f.write(code_str)
result = pytest.main([path, *(args or [])])
return result
helper_code = 'def test_named_ok():\n assert 1 + 1 == 2\n'
exit_code = run_pytest_named(helper_code, 'test_naming_demo.py', ['-v', '--no-header'])
print(exit_code)
Part 7: A File pytest Will NOT Collect#
bad_name_code = 'def test_should_be_skipped():\n assert False\n'
exit_code = run_pytest_named(bad_name_code, 'helpers_demo.py', ['-v', '--no-header'])
print(exit_code)
Part 8: Useful CLI-Style Flags - -v, -x, -k#
code = '''
def test_one():
assert True
def test_two():
assert False
def test_three():
assert True
'''
print('--- with -x (stop at first failure) ---')
run_pytest(code, ['-v', '--no-header', '-x'])
print('--- with -k test_three (only matching tests) ---')
run_pytest(code, ['-v', '--no-header', '-k', 'test_three'])
Part 9: A Real Pattern - Testing a Small Utility Module#
module_code = '''
def slugify(text):
return text.strip().lower().replace(' ', '-')
def truncate(text, length):
if len(text) <= length:
return text
return text[:length].rstrip() + '...'
'''
test_code = module_code + '''
def test_slugify_basic():
assert slugify('Hello World') == 'hello-world'
def test_slugify_strips_whitespace():
assert slugify(' Padded Text ') == 'padded-text'
def test_truncate_short_text_unchanged():
assert truncate('short', 10) == 'short'
def test_truncate_long_text_gets_ellipsis():
result = truncate('this is a long sentence', 10)
assert result.endswith('...')
assert len(result) == 12
'''
exit_code = run_pytest(test_code, ['-v', '--no-header'])
print(exit_code)
Wrap-Up: What You Learned#
- pytest tests are plain functions named test_*, with no special base class required.
- Plain assert statements work directly; pytest rewrites them to show detailed left/right failure values.
- A test file can hold as many test_ functions as needed, each reported pass or fail independently.
- Discovery relies on naming: files test_.py or test.py, functions test, classes Test without init.
- Passing an explicit path collects that file directly, bypassing the naming-based discovery rules.
- -v gives verbose per-test output, -x stops at the first failure, -k filters tests by name.
- pytest.main() returns an exit code: 0 means every test passed, non-zero means at least one failed.
- That wraps up getting started. Next up: Fixtures Basics.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



