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

Thursday

When checks are missing
// Step by step across the field, with tests at the edges
⏱ about 30 min

Thursday: When Checks Are Missing

Comet shows the crew her first move() from last night. "It is shorter," she says proudly. "I left out the grid check. Pip just goes."

Wren reads it twice. "What does the evidence say?" he asks. "Let's run it next to a rock and next to an edge, and look at where Pip ends up."

Nova projects the field with Pip sitting on top of a rock. "How can I help?" she asks. "Compare the runs with your real rover.py. Look for the quiet ones."

Comet laughs at the picture. "Fine, that is data. Lead programmer, which of these runs should worry us most?"

Comet's shorter move()

Here is move() in rover_nocheck.py, a copy of rover.py without the is_open check. The real rover.py is never changed.

Common security issues in programs include missing bounds checks and poor input validation. Today you see both.

    def move(self, grid):
        """Step forward one cell if it is open. Gives back True or False."""
        d_row, d_col = STEPS[self._heading]
        new_row = self._row + d_row
        new_col = self._col + d_col
        if self._battery < self.step_use():
            self._log.append("battery low")
            return False
        self._row = new_row
        self._col = new_col
        self._battery -= self.step_use()
        self._log.append(f"moved to ({new_row}, {new_col})")
        return True
# nocheck_drive.py
from field_data import FIELD
from grid import FieldGrid
from rover_nocheck import Rover

grid = FieldGrid(FIELD)
first = Rover("Pip", 1, 0)
first.move(grid)
print(first.status())
print("Cell under Pip:", grid.cell(first.get_row(), first.get_col()))
second = Rover("Pip")
second.turn_left()
second.move(grid)
print(second.status())
print("Cell under Pip:", grid.cell(second.get_row(), second.get_col()))
PREDICT NOCHECK_DRIVE.PY
  • Read the question.
  • Tap your answer.
Where does the first Pip end?
What does the second Cell under Pip line show?
Pip at (1, 1) facing E, battery 95
Cell under Pip: #
Pip at (-1, 0) facing N, battery 95
Cell under Pip: .

Now a third Pip starts at (4, 0), turns to face S, and drives off the bottom edge:

# nocheck_edge.py
from field_data import FIELD
from grid import FieldGrid
from rover_nocheck import Rover

grid = FieldGrid(FIELD)
third = Rover("Pip", 4, 0)
third.turn_right()
third.move(grid)
print(third.status())
print("Cell under Pip:", grid.cell(third.get_row(), third.get_col()))
Pip at (5, 0) facing S, battery 95
Traceback (most recent call last):
  File "nocheck_edge.py", line 11, in <module>
    print("Cell under Pip:", grid.cell(third.get_row(), third.get_col()))
                             ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "grid.py", line 23, in cell
    return self._cells[row][col]
           ~~~~~~~~~~~^^^^^
IndexError: list index out of range

Loud and quiet errors

A run-time error happens while the program runs; IndexError is one. It is loud: the program stops and names the line.

A logic error makes a program behave wrongly. Pip on a rock is quiet: nothing stops, and only testing finds it.

RunWhat happenedKind of error
first Pipstood on the rock at (1, 1)logic error (quiet)
second Pipat row -1, reading the last rowlogic error (quiet)
third Pipcell(5, 0) stopped with IndexErrorrun-time error (loud)
StatementTrue or false?
Without the check, Pip can stand on a rock.?
Every missing bounds check makes the program stop.?
An IndexError is a run-time error.?
The real rover.py was changed for these runs.?
WHY THIS EXERCISEMissing checks can fail loudly or quietly, and quiet logic errors need tests to be found.

Poor input validation

Input needs checks too. This program trusts a typed start row:

# start_nocheck.py
from rover import Rover

start = int(input("Start row: "))
pip = Rover("Pip", start, 0)
print(pip.status())

Pip now starts off the field, and nothing complains. This version checks the text first:

# start_check.py
from field_data import FIELD
from grid import FieldGrid
from rover import Rover

grid = FieldGrid(FIELD)
text = input("Start row: ")
if text.isdigit() and grid.in_bounds(int(text), 0):
    pip = Rover("Pip", int(text), 0)
    print(pip.status())
else:
    print("Start row must be 0 to", grid.height() - 1)
PREDICT START_CHECK.PY
  • Read the question.
  • Tap your answer.
The learner types 2. What is the second line?
The learner types 9. What is the second line?
The learner types two. What is the second line?
At your computer
1. In your fieldsim folder, save a copy of rover.py named rover_nocheck.py. In the copy only, delete the three lines of the is_open check in move().
2. Type nocheck_drive.py and nocheck_edge.py. Run each one and compare with the outputs above.
3. Type start_check.py and run it three times: type 2, then 9, then two.
4. Change in_bounds(int(text), 0) to in_bounds(int(text), 9). Predict what 2 gives now, run it, then change it back.

You found quiet bugs on purpose, which is how real testers work. Tomorrow you write the tests.

← Wednesday