In the previous step we showed how to debug a problem.
Let's show how to write a test that recreates the problem -- and ensures our Python code handles it correctly -- by using
pytest exception assertions.
We'll then refactor the code to detect that situation and return
None, writing tests before doing the refactoring.
We start, as always, with a test.
We're adding a new test
test_player.py, to detect the case when no guardians have been assigned:
import pytest# ....def test_no_primary_guardian(player_one):with pytest.raises(IndexError) as exc:player_one.primary_guardianassert 'list index out of range' == str(exc.value)
As we type the code above, don't forget to use autocomplete to let PyCharm generate
import pytest for you.
This test uses a special context manager facility in
pytest, in which you run a block of code that you expect to raise an exception, and let
pytest handle it.
You test will fail if the exception is not raised.
The context manager optionally lets you add
as exc to then do some asserts after the block, about the nature of the exception value.
Perhaps we decide that raising an exception isn't a good pattern.
Instead, we want to detect if
self.guardians is empty, and if so, return
To start, let's...write a test. Or in this case, change that last test:
def test_construction(player_one):assert 'Tatiana' == player_one.first_nameassert 'Jones' == player_one.last_nameassert  == player_one.guardiansdef test_add_guardian(player_one, guardians):player_one.add_guardian(guardians)assert [guardians] == player_one.guardiansdef test_add_guardians(player_one, guardians):player_one.add_guardian(guardians)player_one.add_guardians((guardians, guardians))assert list(guardians) == player_one.guardiansdef test_primary_guardian(player_one, guardians):player_one.add_guardian(guardians)player_one.add_guardians((guardians, guardians))assert guardians == player_one.primary_guardiandef test_no_primary_guardian(player_one):assert player_one.primary_guardian is None
Good news, the test fails.
Remember to remove the now-unused
import pytest via PyCharm's
We now change our implementation in
player.py to correctly return
While we're at it, let's put a return type on
from dataclasses import dataclass, fieldfrom typing import List, Iterable, Optionalfrom laxleague.guardian import Guardian@dataclassclass Player:""" A lacrosse player in the league """first_name: strlast_name: strguardians: List[Guardian] = field(default_factory=list)def add_guardian(self, guardian: Guardian):self.guardians.append(guardian)def add_guardians(self, guardians: Iterable[Guardian]):self.guardians.extend(guardians)@propertydef primary_guardian(self) -> Optional[Guardian]:return self.guardians if self.guardians else None
Python type hinting uses
Optional when the value might be
Our tests now pass which means we did the refactoring safely.