← Back to Programming

Classes and testing

As projects grow, classes organise related data and behaviour, and automated tests check that code keeps working as it changes.

Defining a class

A class is a blueprint for objects: class Dog: def __init__(self, name): self.name = name; def speak(self): return f"{self.name} says woof". __init__ runs when an object is created, and self refers to that particular object. rex = Dog("Rex") creates an instance; rex.speak() returns "Rex says woof".

Attributes and methods

Attributes store an object's data, such as self.name; methods are functions defined in the class that act on the object. Each instance has its own attributes, so two Dog objects can have different names. Class attributes, defined directly in the class body, are shared by all instances.

Inheritance

A class can inherit from another: class Puppy(Dog): def speak(self): return f"{self.name} says yip". Puppy gets everything Dog has but overrides speak. super().__init__(name) calls the parent's initialiser when the child adds attributes. Inheritance suits clear 'is a' relationships.

Special methods

Methods with double underscores customise behaviour. __str__ controls how print shows an object; __eq__ defines ==; __len__ lets len() work. The dataclasses module can generate __init__, __repr__, and __eq__ automatically: @dataclass class Point: x: float; y: float.

Why test

Tests are code that checks other code. They catch bugs early, give confidence when changing code, and document how functions should behave. Without tests, every change risks quietly breaking something that used to work, a regression.

Writing tests

The simplest check is assert: assert add(2, 3) == 5 raises an error if false. The popular pytest tool runs every function named test_ something in files named test_*.py and reports failures clearly. Good tests cover normal cases, edge cases such as empty lists and zero, and expected errors, using pytest.raises.

Test-driven development

In test-driven development (TDD), you write a failing test first, then write just enough code to pass it, then refactor to improve the design while keeping tests green. The cycle, red, green, refactor, keeps code tested and focused on requirements. Even without strict TDD, writing tests alongside code is good practice.

Worked example

class BankAccount: def __init__(self, balance=0): self.balance = balance; def withdraw(self, amount): if amount > self.balance: raise ValueError("Insufficient funds"); self.balance -= amount. Tests: def test_withdraw(): acct = BankAccount(100); acct.withdraw(30); assert acct.balance == 70. def test_overdraw(): with pytest.raises(ValueError): BankAccount(10).withdraw(50).

Key takeaways

Classes are blueprints; __init__ initialises new objects, and self refers to the instance. Attributes hold data; methods define behaviour; class attributes are shared. Inheritance reuses and overrides behaviour; super() calls the parent. Special methods such as __str__ and __eq__ customise objects; dataclasses reduce boilerplate. Tests catch bugs and regressions. pytest runs test_ functions; test edge cases and errors. TDD follows red, green, refactor.

Flashcard

Class

Blueprint for objects

class Dog:.

Check your understanding

In a Python method, what does self refer to?

← Previous lesson Next lesson →