Testing programs with objects; inheritance
Testing programs with objects
How should we test our library program? We should write tests for each of
the methods. Let’s test the Book class:
from library import * def test_init(): b = Book("The Nickel Boys", "Colson Whitehead") assert b.title == "The Nickel Boys" assert b.author == "Colson Whitehead" def test_matches(): b = Book("The Nickel Boys", "Colson Whitehead") assert b.matches("Nickel") assert not b.matches("Parasite") assert b.matches("Colson")
Notice the pattern here: we’re creating objects in our test methods in order to test their methods.
Adding checkout
Let’s say we want to add “checkout” behavior to our library: we want to be able to record that certain items have been checked out or returned, and take this into account when searching. Let’s add this behavior to our tests first:
def test_init(): b = Book("The Nickel Boys", "Colson Whitehead") assert b.title == "The Nickel Boys" assert b.author == "Colson Whitehead" assert not b.checked_out def test_checkout(): b = Book("The Nickel Boys", "Colson Whitehead") b.checkout() assert b.checked_out def test_return(): b = Book("The Nickel Boys", "Colson Whitehead") b.checkout() b.return() assert not b.checked_out def test_matches(): b = Book("The Nickel Boys", "Colson Whitehead") assert b.matches("Nickel") assert not b.matches("Parasite") assert b.matches("Colson") b.checkout() assert not b.matches("Nickel") assert not b.matches("Parasite") assert not b.matches("Colson")
Then we can add the behavior to our Book class:
class Book: def __init__(self, title: str, author: str): self.title = title self.author = author self.checked_out = False def checkout(self): self.checked_out = True def return(self): self.checked_out = False def matches(self, query: str) -> bool: return (not self.checked_out) and (query in self.title or query in self.author)
Inheritance
Right now, we’re considering each class to be totally separate, with no shared
code or data. For instance, even though Book~s and ~Movie~s both implement a
~matches method, the two methods do are have completely different
implementations. Inheritance gives us a way to share code between
objects. Classes can inherit from other classes, like this:
class A: pass class B(A): pass
When there’s an inheritance relationship like this between two classes, we sat
that A is the superclass of B and B is a subclass of A.
Let’s see an example of inheritance in action. Let’s say our library wants to
track which items have been checked out, and only return items in a search if
they are actually available. First, we’ll implement a LibraryItem class, which
we’ll use as the superclass for both books and movies:
class LibraryItem: def __init__(self): self.checked_out = False def checkout(self): self.checked_out = True def return(self): self.checked_out = False
The LibraryItem class only handles checking items out and back in–it doesn’t
know anything about what the items actually are.
In order to use our new superclass, we’ll make some changes to the Book and
Movie classes:
class Book(LibraryItem): def __init__(self, title: str, author: str): self.title = title self.author = author super().__init__(self) class Movie(LibraryItem): def __init__(self, title: str, director: str, actors: list): self.title = title self.director = director self.actors = actors super().__init__(self)
Both Book and Movie objects will now have a checked_out field, as well as
the checkout and return methods. We can use the field in each class’s
matches method:
class Book(LibraryItem): # other methods elided... def matches(self, query: str) -> bool: return (not self.checked_out) and (query in self.title or query in self.author) class Movie(LibraryItem): # other methods elided... def matches(self, query: str) -> bool: return (not self.checked_out) and query in self.title or query in self.director or any([query in actor for actor in self.actors])