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])