← Back to course
Python 11-12 / Week 06 / Friday
5/6
Week 06 Β· Drive and Test

Friday

Trace Friday: Tests at the edges
// Step by step across the field, with tests at the edges
⏱ about 30 min

Friday: Trace Friday: Tests at the Edges

Comet wants to add a B command that turns Pip all the way around. "Two right turns," she says. "Easy. Let's add it now."

Wren holds up a page of assert lines. "What does the evidence say after you add it?" he asks. "If every old test still passes, we know nothing else broke."

Nova projects a folder icon splitting into two. "How can I help?" she asks. "Try the new idea in a copy of the folder. Keep the real one safe."

"Deal," Comet says. "Lead programmer, the tests come first. Then my B."

test_fieldsim.py

Testing and refining is a deliberate, repeated process: debugging, and comparing what a program does with what it should do.

Each test function below sets up a small case and checks it with assert. The tests aim at the edges: a corner, a rock, off the field.

# test_fieldsim.py
# Tests for FieldSim. Run this file: it prints one line when all pass.
from field_data import FIELD
from grid import FieldGrid
from rover import Rover
from sample import Sample


def test_grid():
    grid = FieldGrid(FIELD)
    assert grid.height() == 5
    assert grid.width() == 6
    assert grid.is_open(0, 0)
    assert not grid.is_open(1, 1)
    assert not grid.is_open(-1, 0)
    assert not grid.is_open(0, 6)


def test_turns():
    rover = Rover("Test")
    rover.turn_right()
    assert rover.get_heading() == "S"
    rover.turn_left()
    rover.turn_left()
    assert rover.get_heading() == "N"


def test_move():
    grid = FieldGrid(FIELD)
    rover = Rover("Test")
    assert rover.move(grid)
    assert (rover.get_row(), rover.get_col()) == (0, 1)
    assert rover.get_battery() == 95
    rover.turn_left()
    assert not rover.move(grid)
    assert rover.get_row() == 0


def test_pick_up():
    rover = Rover("Test")
    for n in range(3):
        assert rover.pick_up(Sample(f"T{n}", "clay", 10))
    assert not rover.pick_up(Sample("T3", "clay", 10))
    assert rover.total_grams() == 30


test_grid()
test_turns()
test_move()
test_pick_up()
print("All FieldSim tests passed")
All FieldSim tests passed
TestEdge case it checks
test_gridthe corner (0, 0) is open; a rock; row -1 and column 6 are off the field
test_turnsturning right then left twice gives the right headings
test_moveone open step uses 5 battery; a step off the top edge is refused
test_pick_upthree samples fit and the fourth does not
READ THE TESTS
  • Read the question.
  • Tap your answer.
In test_move, after rover.turn_left(), which heading does the rover face?
Why does test_move check rover.get_row() == 0 at the end?

A test that fails: here test_move expects battery 90 instead of 95. The run compares what happened with what the test said should happen:

Traceback (most recent call last):
  File "test_fieldsim.py", line 49, in <module>
    test_move()
    ~~~~~~~~~^^
  File "test_fieldsim.py", line 33, in test_move
    assert rover.get_battery() == 90
           ^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
What kind of error does a failing assert give?
Which test function failed? Name it.

An edge case: exactly enough battery. System reliability means a program does its job as expected without failing. Programmers test with many conditions to improve it, including the limits.

# test_battery.py
# Edge case: the battery has exactly enough for one more step.
from grid import FieldGrid
from rover import Rover


def test_battery_edge():
    grid = FieldGrid(["." * 22])
    rover = Rover("Test")
    for n in range(20):
        assert rover.move(grid)
    assert rover.get_battery() == 0
    assert not rover.move(grid)
    assert rover.get_log()[-1] == "battery low"


test_battery_edge()
print("Battery edge test passed")
TRACE TEST_BATTERY.PY
  • Read the question.
  • Tap your answer.
Before the 20th move, the battery is 5. Does the 20th move work?
Why is the test grid one row of 22 open cells?

Add a feature in a copy

Comet's B command goes into a copy of the whole folder, fieldsim_try. In the copy, run() gets one more branch, and the tests get test_back:

    def run(self, commands, grid):
        """Follow a command string: F forward, L left, R right."""
        for c in commands:
            if c == "F":
                self.move(grid)
            elif c == "L":
                self.turn_left()
            elif c == "R":
                self.turn_right()
            elif c == "B":
                self.turn_right()
                self.turn_right()
            else:
                self._log.append(f"unknown command {c}")
def test_back():
    rover = Rover("Test")
    rover.run("B", FieldGrid(FIELD))
    assert rover.get_heading() == "W"

In fieldsim_try, test_fieldsim.py also calls test_back() just before the last print. The run:

All FieldSim tests passed
StatementTrue or false?
After adding B, all the old tests still pass.?
Rerunning old tests after a change can catch something that broke.?
The B command is added to the real rover.py in fieldsim.?
An edge case tests a limit, like exactly enough battery.?
WHY THIS EXERCISENew features can break old ones, so the whole test file runs again, and the real folder stays unchanged.
At your computer
1. In your fieldsim folder, type test_fieldsim.py and run it. Check for All FieldSim tests passed.
2. Type test_battery.py in the same folder and run it.
3. Copy the whole fieldsim folder and name the copy fieldsim_try. Open rover.py and test_fieldsim.py from the copy.
4. In the copy, add the B branch to run(). Add test_back after test_pick_up, call test_back() before the last print, and run the tests.
5. Back in fieldsim, change 95 to 90 in test_move. Run it, read the AssertionError, then change it back.
  1. STEPS turns a heading into a row change and a column change.
  2. move() checks is_open and the battery first, then steps, logs and returns True.
  3. run() follows F, L and R, and logs any other letter as an unknown command.
  4. Missing checks fail loudly (IndexError) or quietly (a rover on a rock).
  5. Check typed input before trusting it.
  6. Tests at the edges, rerun after every change, show nothing else broke.

Your rover drives and your tests guard it. Tomorrow is a Mission Quest with a coin and the paper field.

← Thursday