AI Tester Blueprint Foundations Python for testers
Chapter 11
Chapter 11 . Foundations . Python for testers

Python for testers: run it, break it, fix it

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.

185
Python files
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.

TopicFoldersFilesWhat it coversStart with
Basicsex_01 to ex_0330print() with sep and end, comments, the 35 keywords, identifier rules, dynamic typing, literals in four bases, input() always returns str, string methods, escapes, raw stringsLab003_Print.py, Lab013_Multiple_Prints.py, Lab028_String_Double_Single_Diff.py
Operators and control flowex_04 to ex_0727//, %, **, divmod, no ++, the ternary, membership; if / elif / else; match / case with case _; for + range, while, break, continue, passLab040_Operators_P9.py, LabSwitch02.py, Lab059.py
Functionsex_08 to ex_1228Four function shapes, defaults, keyword arguments, tuple returns, *args, local and global scope, decorators, type conversion, lambdasLab071_IQ.py, Lab077_Local_Var.py, Lab083.py
Collectionsex_13 to ex_1725List methods, tuples as frozen config, sets and frozenset, map() and filter() on test results, dict CRUD, nested records, countingLab098_POP.py, 107_Lab.py, 117_IQ.py
Object-oriented Pythonex_18 folders 01 to 0840Classes and self, constructors, encapsulation, five kinds of inheritance and the MRO, overriding, "overloading" with defaults, ABC contracts, static and class methods136_REAL.py, 141_MOR.py, 147_REAL_Browser.py
Exceptions and modulesex_18 folders 09 and 10, ex_1923The common built-in errors, try / except / else / finally, raise, custom exceptions, ExceptionGroup, os, a package with __init__.py165.py, 167.py, ex_19_Package/170.py
Files and test dataex_209Counter, defaultdict, the __main__ guard, os.path, reading files, csv and pandas, python-dotenv171.py, 177.py, 178.py
pytestex_213test_*.py discovery, plain assert, markers, -m versus -k, plus a 300-line cheat sheettest_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
What does python3 file.py print?
data-testid=py-questiondata-testid=py-opt-1data-testid=py-checkdata-testid=py-nextdata-testid=py-reset
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

    def setup(self):
        print(f"Launching {self.browser}")

class LoginTest(BaseTest):
    def run_test(self):
        self.setup()
        print("Running login test...")

class SignupTest(BaseTest):
    def run_test(self):
        self.setup()
        print("Running signup test...")


t = LoginTest("chrome")
t.run_test()

t = SignupTest("firefox")
t.run_test()
output
Launching chrome
Running login test...
Launching firefox
Running signup 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

ex_21_PyTest/test_181.py
import pytest

@pytest.mark.smoke
def test_method2():
    print("test1")
    assert 1-1 == 2

@pytest.mark.regression
def test_login():
    print("test2")
    assert 1 + 1 == 2
python3 -m pytest test_180.py test_181.py (last line)
=================== 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:

FileThe line that mattersWhat happens
Lab083.py, Lab082.pyreturn wrapper at the decorator's levelCorrect. Calling the function runs the wrapper, which runs the function. Stacked decorators wrap bottom-up and run top-down.
Lab080_Decor.pyreturn wrapper() with parenthesesThe wrapper runs once, at decoration time, and prints its three lines on import. test_ui becomes None (probed with print(test_ui)).
Lab079_Decortors.pyreturn wrapper() indented inside wrapperThe 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


def print_logs(func):
    def wrapper():
        print("Start the logs")
        func()
        print("End of the log")
    return wrapper

def time_decorator(func):
    def wrapper():
        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_logs
def test_ui_1():
    print("Add a function, time taken by this function 1")
    time.sleep(2)

@time_decorator
@print_logs
def test_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 README
python3 -m pip install python-dotenv requests   # ex_18 encapsulation and exceptions
python3 -m pip install pandas                   # ex_20/179.py
python3 -m pip install pytest                   # ex_21

cd ../ex_21_PyTest
python3 -m pytest test_180.py test_181.py -v
Command (in ex_21_PyTest)Result on pytest 9.1.1
python3 -m pytest test_180.py test_181.py1 failed, 3 passed, 4 warnings
python3 -m pytest -m smoke test_180.py test_181.py1 failed, 1 passed, 2 deselected, 4 warnings
python3 -m pytest -m "not smoke" test_180.py test_181.py2 passed, 2 deselected, 4 warnings
python3 -m pytest -k login test_180.py test_181.py1 passed, 3 deselected, 4 warnings
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')

with open(file_path) as csvfile:
    reader = csv.reader(csvfile)
    next(reader)  # skip header row
    for col in reader:
        print(col[0], col[1], sep="|")