Chapter 11 is 185 Python files, most under 30 lines, each built to be run, broken and run again. It takes a manual tester from print() to the pieces every framework later in the course is made of: functions, decorators, classes, exceptions, test data files and pytest.
In 21 exercise folders. The README's "173" counts only ex_01 to ex_19.
21
Exercise folders
In learning order: basics, flow, functions, collections, OOP, files, pytest.
58
OOP and exception files
ex_18_OOPs_Python alone, in 10 topic folders.
4
pytest tests
3 pass and 1 fails on purpose: assert 1-1 == 2.
01Why Python, and why one small file at a time
Every framework later in the course is Python: a CrewAI crew, a LangChain chain, a LangGraph graph, a DeepEval suite, an MCP server. To read them, change them and test them you need a working model of the language, not a tour of it. Chapter 11 builds that model the way a tester learns a system: run a small thing, predict what it does, break it on purpose, fix it.
185 files in 21 folders, most of them under 30 lines. Each shows one idea and prints something you can check.
Some fail on purpose."PRAMOD" + 10 raises TypeError in Lab013_Multiple_Prints.py; Lab015_IQ.py fixes it with str(). A few fail by accident, which makes them better drills (card 07).
Tester-flavoured examples. A BaseTest class with setup(), a match on "API", "UI", "Performance", "Security", filter() over PASS and FAIL results, response times converted with map(), CSV login data, pytest markers.
Version: Python 3.11 or newer (the README). The floor comes from match / case (3.10), dict merge with | (3.9), Counter.total() (3.10) and ExceptionGroup (3.11). The outputs on this page come from Python 3.12.7.
02The 185 files, grouped by topic
Follow the folders in order. File numbers run from Lab001 to 181, with gaps and reuses (two Lab040_*, numbers 130 to 136 in two OOP folders, 170.py and 179.py twice), so trust the folder first and the number second.
Topic
Folders
Files
What it covers
Start with
Basics
ex_01 to ex_03
30
print() with sep and end, comments, the 35 keywords, identifier rules, dynamic typing, literals in four bases, input() always returns str, string methods, escapes, raw strings
//, %, **, divmod, no ++, the ternary, membership; if / elif / else; match / case with case _; for + range, while, break, continue, pass
Lab040_Operators_P9.py, LabSwitch02.py, Lab059.py
Functions
ex_08 to ex_12
28
Four function shapes, defaults, keyword arguments, tuple returns, *args, local and global scope, decorators, type conversion, lambdas
Lab071_IQ.py, Lab077_Local_Var.py, Lab083.py
Collections
ex_13 to ex_17
25
List methods, tuples as frozen config, sets and frozenset, map() and filter() on test results, dict CRUD, nested records, counting
Lab098_POP.py, 107_Lab.py, 117_IQ.py
Object-oriented Python
ex_18 folders 01 to 08
40
Classes and self, constructors, encapsulation, five kinds of inheritance and the MRO, overriding, "overloading" with defaults, ABC contracts, static and class methods
136_REAL.py, 141_MOR.py, 147_REAL_Browser.py
Exceptions and modules
ex_18 folders 09 and 10, ex_19
23
The common built-in errors, try / except / else / finally, raise, custom exceptions, ExceptionGroup, os, a package with __init__.py
165.py, 167.py, ex_19_Package/170.py
Files and test data
ex_20
9
Counter, defaultdict, the __main__ guard, os.path, reading files, csv and pandas, python-dotenv
171.py, 177.py, 178.py
pytest
ex_21
3
test_*.py discovery, plain assert, markers, -m versus -k, plus a 300-line cheat sheet
test_180.py, test_181.py
flowchart LR
A["ex_01-03<br/>basics"] --> B["ex_04-07<br/>operators, flow"]
B --> C["ex_08-12<br/>functions"]
C --> D["ex_13-17<br/>collections"]
D --> E["ex_18<br/>OOP"]
E --> F["ex_18, ex_19<br/>errors, modules"]
F --> G["ex_20<br/>files, test data"]
G --> H["ex_21<br/>pytest"]
The learning path, folder by folder. The README's own diagram goes one folder at a time; this is the same order, grouped.
03Live demo: predict the output
Ten files from the repo, shown verbatim. Read the code, pick what python3 prints, then check. A wrong pick shows the real output and why. Two of the ten fail when run; one of those is a bug in the repo.
Predict the output: ten files from the repoNo API key needed
Every answer on this demo was produced by running the file with python3: the page's build compares each one with a real run. No Python runs in your browser.
04Four files worth reading line by line
match / case routing a test type
ex_06_Switch_Match/LabSwitch02.py
print("Enter the which Test you want to run")
test_type = input("Enter the Test Type : API, UI, Performance, Security ")
match test_type:
case"API":
print("We are running a POSTMAN API Testcase.")
case"UI":
print("We are running a Selenium Testcase.")
case"Performance":
print("We are running a Performance Testcase.")
case"Security":
print("We are running a Security Testcase.")
case _:
print("Invalid Type.")
It reads the test type from the keyboard. Typing UI prints We are running a Selenium Testcase.; anything unlisted falls to case _. There is no fall-through between cases, unlike switch in Java.
A BaseTest through inheritance
ex_18_OOPs_Python/05_Inheritance/136_REAL.py
class BaseTest:
def__init__(self, browser):
self.browser = browser
defsetup(self):
print(f"Launching {self.browser}")
classLoginTest(BaseTest):
defrun_test(self):
self.setup()
print("Running login test...")
classSignupTest(BaseTest):
defrun_test(self):
self.setup()
print("Running signup test...")
t = LoginTest("chrome")
t.run_test()
t = SignupTest("firefox")
t.run_test()
This is the shape of every test framework: setup lives once in the parent, each test class only adds its own steps.
The full try / except / else / finally
ex_18_OOPs_Python/09_Exceptions/165.py
try:
a = int(input("Enter num 1"))
b = int(input("Enter num 2"))
c = a / b
except ValueError:
print("Value Error")
except ZeroDivisionError:
print("Div Error")
else: # Runs only if try block succeeds.print(c)
finally:
print("I will always execute!")
flowchart LR
T["try: read a and b, c = a / b"] -->|"not a number"| V["except ValueError: Value Error"]
T -->|"b is 0"| Z["except ZeroDivisionError: Div Error"]
T -->|"no error"| E["else: print(c)"]
V --> F["finally: I will always execute!"]
Z --> F
E --> F
The three paths through 165.py. else runs only when try succeeds; finally runs every time.
pytest markers, and one test that fails on purpose
=================== 1 failed, 3 passed, 4 warnings in 0.17s ====================
The failure is test_method2 (assert (1 - 1) == 2). The four warnings are PytestUnknownMarkWarning: the markers reg, smoke and regression are not registered.
05Decorators: the shape that trips people up
A decorator is how a framework wraps setup and teardown around a test. The repo has the right shape and two wrong ones side by side, and the difference is one line:
File
The line that matters
What happens
Lab083.py, Lab082.py
return wrapper at the decorator's level
Correct. Calling the function runs the wrapper, which runs the function. Stacked decorators wrap bottom-up and run top-down.
Lab080_Decor.py
return wrapper() with parentheses
The wrapper runs once, at decoration time, and prints its three lines on import. test_ui becomes None (probed with print(test_ui)).
Lab079_Decortors.py
return wrapper() indented inside wrapper
The decorator returns None, so the call raises TypeError: 'NoneType' object is not callable. Question 5 in the demo.
ex_10_Decortors/Lab082.py
import time
defprint_logs(func):
defwrapper():
print("Start the logs")
func()
print("End of the log")
return wrapper
deftime_decorator(func):
defwrapper():
start_time = time.time()
print(start_time)
func()
end_time = time.time()
print(end_time)
print("Total Time Take by Func -> ", end_time - start_time)
return wrapper
@time_decorator@print_logsdeftest_ui_1():
print("Add a function, time taken by this function 1")
time.sleep(2)
@time_decorator@print_logsdeftest_ui_2():
print("Add a function, time taken by this function 2")
time.sleep(5)
test_ui_1()
test_ui_2()
sequenceDiagram
participant C as caller
participant T as time_decorator.wrapper
participant L as print_logs.wrapper
participant F as test_ui_1 body
C->>T: test_ui_1()
T->>T: start_time = time.time()
T->>L: func()
L->>L: print("Start the logs")
L->>F: func()
F-->>L: prints, then sleeps 2 s
L->>L: print("End of the log")
L-->>T: returns
T->>T: print the total, about 2.0 s
How the two decorators in Lab082.py run: the outer timer measures everything the inner logger does.
06Run it
Run each file from its own folder: several open files by a relative path. Most files need only the standard library.
terminal
cd chapter_11_Python_Learning/ex_01_Python_Basics
python3 Lab001_Hello.py
# the few third-party packages, per the READMEpython3 -m pip install python-dotenv requests # ex_18 encapsulation and exceptionspython3 -m pip install pandas # ex_20/179.pypython3 -m pip install pytest # ex_21cd ../ex_21_PyTest
python3 -m pytest test_180.py test_181.py -v
23 files wait for keyboard input (for example Lab022_User_Input_Sum_Of_Two_numbers.py, LabSwitch02.py and the exceptions 159.py to 165.py), so run them in a terminal, not a "run without input" button. 164.py sends a real HTTP request to the URL you type.
07Bugs and gotchas in the repo
These are real, and each one is a better drill than a made-up exercise. All were confirmed by running the file, except where noted.
Lab079_Decortors.py: return wrapper() is indented inside wrapper, so the decorated call raises TypeError: 'NoneType' object is not callable.
08_Static/154_Ex.py: total() returns cls.count, but the class attribute is counter. Calling it gives AttributeError: type object 'Counter' has no attribute 'count'. Did you mean: 'counter'?
Lab031_Arth_Op.py: the comments label a // b as "R" and a % b as "Q". It is the other way round: // is the quotient (3) and % the remainder (1). Lab035_Operators_P4.py gets it right.
Lab099_Tuple.py: the demonstration line my_tuple[0] = 12 is not commented out, so nothing after it runs.
ex_20/178.py and 179.py: they open TD.csv, but the file is td.csv. That works on macOS's default case-insensitive disk and fails on a case-sensitive one, such as most Linux CI machines (not run on Linux for this page).
ex_20/174_OS.py: joins ex_20_Collections/pramod.txt, but the folder is ex_20_Collections_FileIO, so it raises FileNotFoundError from anywhere.
ex_21: the markers are not registered, and test_180.py uses reg where test_181.py uses regression.
Secrets. A few encapsulation and .env files print a password or compare one hard-coded in the file. Read them for the mechanics; never copy the pattern. Secrets come from the environment and are never printed.
ex_20_Collections_FileIO/178.py
import csv
import os
base_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(base_dir, 'TD.csv')
withopen(file_path) as csvfile:
reader = csv.reader(csvfile)
next(reader) # skip header rowfor col in reader:
print(col[0], col[1], sep="|")
DDrills for the chapter
Drills 1 to 6 change files in chapter_11_Python_Learning (work on a copy). Drills 7 to 9 automate the quiz on this page: every control has a data-testid (turn on Show locator badges to see them).
Fix the broken decorator
Make ex_10_Decortors/Lab079_Decortors.py print the four safety lines around the ride instead of raising TypeError.
Hint
One line moves and loses its parentheses.
Expected result
Dedent the return to the decorator's level and return the function itself: return wrapper. The file then prints:
output
1.Before the function is called.
2.Add Helmet, Dashcash, gloves, knee guards, License
I am driving ola scooter
3.After the function is called.
4.Secure Driving, Leave all the items
Call the class method
Add print(Counter.total()) to the end of 08_Static/154_Ex.py and run it. What happens, and what is the fix?
Expected result
AttributeError: type object 'Counter' has no attribute 'count'. Did you mean: 'counter'? Return cls.counter and it prints 0.
Let the tuple file finish
Make ex_14_Tuple/Lab099_Tuple.py run to the end without deleting its lesson.
Expected result
Comment out line 3 (my_tuple[0] = 12). Output: (1, 2, 3), ('Pramod', 34, True, 9.8), <class 'tuple'>. The last line proves (3,) is a tuple because of the comma.
Silence the pytest warnings
Make python3 -m pytest test_180.py test_181.py run without warnings, without touching the tests.
Hint
The cheat sheet in the same folder shows where markers are registered.
Expected result
Add a pytest.ini that registers smoke, regression and reg (Solution tab): 1 failed, 3 passed and no warnings. Registering only smoke and regression, as the cheat sheet does, still leaves 1 warning for reg.
Run only the smoke tests
Run just the tests marked smoke. How many run, and what is the result?
Change ex_20_Collections_FileIO/178.py so it works on a case-sensitive file system.
Expected result
Open td.csv, the file's real name: os.path.join(base_dir, 'td.csv'). The path is already built from __file__, so it then runs from any folder on any OS.
Score a right answerPlaywright
Write a Playwright test: answer question 1 correctly and assert the score.
Hint
Locators: py-question (value loops), options py-opt-1 to py-opt-4 (radio inputs), py-check, py-status, py-score, and the progress chips py-q-1 to py-q-10.
Expected result
Option 2 (1 3 5 7 9) is right: the status says Correct, py-score reads 1 correct, 1 answered of 10 and py-q-1 has data-state="right".
Assert the real outputPlaywright
Pick the scope question, choose a wrong answer, and assert what the page reveals.
Hint
selectOption('scope'); the revealed output is py-real.
Expected result
py-real shows PT and LPB on two lines, the score is 0 correct, 1 answered of 10 and py-check is disabled for that question.
Find an option by its labelPlaywright
On the decorator-bug question, select the right option by its visible text, not by its test id.
Expected result
page.getByLabel("TypeError: 'NoneType' object is not callable").check(): each radio is labelled by the output it shows. Checking it scores a point.
SSolutions: the quiz test and the files to keep
The Playwright spec for the quiz, the pytest files with the warning fix, and two files that show the patterns the framework chapters use. Repo files are verbatim.
# pytest.ini, next to test_180.py and test_181.py (not in the repo: the fix for the warnings)
[pytest]
markers =
smoke: quick sanity checks
regression: full regression suite
reg: the shorter name test_180.py uses
from collections import *
# list -> coCollection of items# tuple -> list but can't modified# set -> no duplicates# dict -> key and value pair# ++ version of the in built, better version of the inbuilt# t = tuple(34, True, 123)# info = namedtuple('info', ['name', 'age', 'ismarried', 'number'])# t = info('pramod', 34, True, 9.8)# print(t)# print(t.name)# print(t.age)# print(t.ismarried)# print(t.number)
c = Counter('abcdeabcdabcaba') # count elements from a stringprint(c.most_common(3))
print(c.total())
from collections import defaultdict
groups = defaultdict(list)
for word in ["apple", "avocado", "banana"]:
groups[word[0]].append(word)
counts = defaultdict(int) # missing -> 0
uniques = defaultdict(set) # missing -> set()
nested = defaultdict(lambda: defaultdict(int))
print(counts)