Lesson 9 · Pytest deep dive
pytest Tutorial #9: Test Organisation That Scales
On testing real Python code with pytest. Test classes, the init gotcha, and xunit-style setup and teardown. Let's get into it. A class named Test-something,…
- CoursePytest deep dive
- Lesson9 of 18
- FormatJupyter notebook · 6 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 9: Test Organization#
- Video nine of an eighteen-part series on testing real Python code with pytest.
- Test classes, the init gotcha, and xunit-style setup and teardown.
- Let's get into it.
Part 1: The Helper From Last Time#
import pytest, 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 2: Grouping Tests With a Test Class#
code = '''
class TestStringMethods:
def test_upper(self):
assert 'hello'.upper() == 'HELLO'
def test_strip(self):
assert ' hi '.strip() == 'hi'
def test_split(self):
assert 'a,b,c'.split(',') == ['a', 'b', 'c']
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 3: A Real Gotcha - a Class With init Is NOT Collected#
code = '''
class TestWithInit:
def __init__(self):
self.value = 1
def test_a(self):
assert self.value == 1
class TestNormal:
def test_b(self):
assert True
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 4: xunit-Style setup_method and teardown_method#
code = '''
events = []
class TestLifecycle:
def setup_method(self, method):
events.append(f'setup for {method.__name__}')
def teardown_method(self, method):
events.append(f'teardown for {method.__name__}')
def test_one(self):
events.append('body one')
def test_two(self):
events.append('body two')
def test_check_events():
assert events == [
'setup for test_one', 'body one', 'teardown for test_one',
'setup for test_two', 'body two', 'teardown for test_two',
]
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 5: setup_class and teardown_class - Once Per Class#
code = '''
build_count = {"n": 0}
class TestSharedConnection:
@classmethod
def setup_class(cls):
build_count["n"] += 1
cls.connection = {'status': 'connected'}
@classmethod
def teardown_class(cls):
cls.connection = None
def test_first(self):
assert self.connection['status'] == 'connected'
def test_second(self):
assert self.connection['status'] == 'connected'
assert build_count["n"] == 1
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Part 6: A Real Pattern - Organizing by Feature Area#
code = '''
def validate_email(email):
return '@' in email and '.' in email.split('@')[-1]
def normalize_email(email):
return email.strip().lower()
class TestEmailValidation:
def test_valid_email_accepted(self):
assert validate_email('user@example.com') is True
def test_missing_at_sign_rejected(self):
assert validate_email('userexample.com') is False
def test_missing_domain_dot_rejected(self):
assert validate_email('user@examplecom') is False
class TestEmailNormalization:
def test_lowercases_email(self):
assert normalize_email('USER@Example.com') == 'user@example.com'
def test_strips_whitespace(self):
assert normalize_email(' user@example.com ') == 'user@example.com'
'''
exit_code = run_pytest(code, ['-v', '--no-header'])
print(exit_code)
Wrap-Up: What You Learned#
- A class named Test-something, with methods named test_something, groups related tests together.
- A Test class defining its own init is silently uncollectable; pytest warns and skips the whole class.
- setup_method and teardown_method run before and after every test method, xunit-style.
- setup_class and teardown_class, marked as classmethods, run once per class, shared by every method.
- Splitting one module into several classes keeps a growing test suite organized by feature area.
- That wraps up test organization. Next up: Parametrized and Indirect Fixtures.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



