- Learn
- Computer Science & IT
- Programming
- Introduction to Programming with Python
- Python Modules and Debugging
Python Modules, Debugging and Testing Basics
math and random, write your own modules and install packages from PyPI with pip. Then read tracebacks and error messages, find bugs with a step-by-step debugging process, and check your functions with assert statements and first unit tests.BeginnerLesson 9 of 10 in Python for Beginners: Introduction to Programming
What you will learn
- Bring ready-made code into a program with import statements, use standard-library modules such as
mathandrandom, split your own code into modules and install third-party packages with pip.- Bring code into a program with import statementsUse
import,from … importandascorrectly, reach names with dot notation and tell the import errors apart. - Calculate and simulate with math, random and other standard modulesUse the main functions of
mathandrandom, avoid their common traps and know a few other everyday standard-library modules. - Write and import your own modulesSplit code into your own
.pyfiles, import them, avoid name clashes and useif __name__ == "__main__":so a file can be both run and imported. - Install and manage packages with pipInstall, inspect, upgrade and remove third-party packages from PyPI, import them under the right name and solve the usual installation problems.
- Bring code into a program with import statements
- Use Python's error output to find where a program failed and why, and tell syntax errors, runtime errors and logic errors apart.
- Read a traceback from the bottom upFind the exception type, the message, the failing line and the chain of calls that led there.
- Recognize common errors and their causesLink the most frequent exception types to their usual causes and fixes, and spot logic errors that produce no message.
- Read a traceback from the bottom up
- Locate the cause of a bug with a step-by-step process instead of guessing, and inspect a running program with print statements and a debugger.
- Follow a step-by-step debugging processReproduce a bug, form and test a hypothesis, narrow down the cause, fix it and retest.
- Inspect a program with print statements, trace tables and a debuggerWatch how values change while code runs, by adding prints, tracing by hand or pausing at breakpoints.
- Follow a step-by-step debugging process
- Use
assertto state what must be true, write small automated tests for your functions and run and read them with the standardunittestmodule.- Check assumptions with assert statementsWrite
assertstatements with clear messages, and know when assertions are the right tool and when they are not. - Write first unit tests for your functionsWrite test functions with assertions, choose typical, boundary and edge cases, and use failing tests to find and fix bugs.
- Organize and run tests with unittestWrite
unittest.TestCasetests, run them and read the report of passes, failures and errors.
- Check assumptions with assert statements
Python Modules and Debugging study guide
1Reuse modules and install packages
Imported code lets a small program use calculations, simulations and tools written elsewhere. You will choose an import form, work with standard-library functions, and arrange your own functions so another file can reuse them. Installation adds a separate question: which Python receives a third-party package? Understanding that link makes it easier to diagnose a missing module and keep each program's names clear.
1.1 Import a module or selected names
A module holds Python code that another program can reuse. Most modules are .py files. The standard library comes with Python; third-party code needs a separate installation. A package groups related modules, commonly in a folder.
Match the call to the import
An import creates names in your program. The form you choose determines how you call the imported code.
| Statement | Name created | Example call |
|---|---|---|
import math | math | math.sqrt(16) |
from math import sqrt | sqrt | sqrt(16) |
import statistics as stats | stats | stats.mean([2, 4, 9]) |
from random import randint as roll | roll | roll(1, 6) |
Dot notation means looking up a name inside a module or object. In math.sqrt(16), Python finds sqrt inside math and calls it with 16. The result is 4.0. The constant math.pi needs no call parentheses because it is a value, not a function.
After import math, sqrt(16) alone raises NameError: the program has a name for the module, but no separate name for sqrt. After only from math import sqrt, the reverse mistake, math.sqrt(16), also raises NameError because math was not bound. An alias replaces the imported binding's usual name: import statistics as stats creates stats, not both names.
Work the same calculation two ways
A right triangle has sides 3 and 4. First square the sides and add them; then take the square root.
import math
length = math.sqrt(3**2 + 4**2)
print(length) # 5.0
The expression inside the call becomes 9 + 16, then 25; sqrt returns 5.0. With from math import sqrt, write length = sqrt(3**2 + 4**2) instead. Using the module prefix makes a function's source visible and keeps its other names out of your program's name space. The binding math itself can still replace an earlier variable called math.
Keep names clear
Avoid from math import *. This wildcard import brings public names into your program without naming them individually. For example, e = 5 followed by that import changes e to 2.718281828459045. An unrelated-looking import can therefore change a later calculation. PEP 8 recommends explicit imports and puts imports at the top, with one module per line: standard library first, third-party packages next, your own modules last.
Python runs a module's top-level code on its first import during a program run. Later imports reuse the loaded module; they do not start it again.
Use the error to choose the fix
| Error | What to inspect |
|---|---|
ModuleNotFoundError from import mathh | The module's spelling and installation |
ImportError from from math import square_root | The name requested from the module |
AttributeError from math.sqroot(16) | The name after the dot |
In the interactive shell, run import math, then dir(math) to list its names. Run help(math.sqrt) to read a function's documentation. The official Python library reference explains its arguments and result.
Key Takeaways
import modulecreates a module binding; reach its contents with dot notation.from module import namecreates the selected binding;aschooses an alias.- Explicit imports keep the origin of names clear and reduce accidental replacements.
- Import errors distinguish an unfound module, an unavailable imported name and a missing attribute.
dir()lists names;help()explains how to use one.
1.2 Choose math and random functions
The standard library supplies tested tools for calculations and simulations. Choosing a function starts with the result you need: an integer count, a rounded measurement, one random item or a reordered list.
Rounding has several meanings
After import math, math.sqrt(16) returns the float 4.0. math.pi and math.e supply the constants 3.141592653589793 and 2.718281828459045. math.factorial(5) multiplies the whole numbers from 1 through 5 and returns 120.
| Expression | Result | Meaning |
|---|---|---|
math.floor(7.9) | 7 | Greatest integer no larger than the value |
math.ceil(2.1) | 3 | Smallest integer no smaller than the value |
math.floor(-2.5) | -3 | Down goes toward negative infinity |
int(-3.5) | -3 | Conversion cuts toward zero |
round(2.5) | 2 | An exact halfway value rounds to even |
round(3.5) | 4 | The neighboring even integer is 4 |
For 23 square meters of wall and 10 square meters per paint can, division gives 2.3 cans. Buying whole cans requires math.ceil(23 / 10), which returns 3. round(2.3) gives 2 and leaves too little paint.
The built-in round() needs no import. For a circle of radius 2, round(math.pi * 2 ** 2, 2) gives 12.57. Rounding changes a number's precision; it is a different job from buying enough whole containers.
Check domains and float comparisons
math.sqrt(-1) raises ValueError because there is no real square root for that input. Calculated floats also need care: binary storage means 0.1 + 0.2 == 0.3 is False. math.isclose(0.1 + 0.2, 0.3) is True; use it for an approximate comparison of calculated floats.
Select the random operation
The random module uses an algorithm to make pseudo-random results.
| Call | Result |
|---|---|
random.randint(1, 6) | An integer from 1 through 6, both included |
random.randrange(1, 6) | An integer from 1 through 5 |
random.random() | A float at least 0.0 and less than 1.0 |
random.uniform(a, b) | A float between the supplied endpoints |
random.choice(items) | One item |
random.sample(items, k) | Items from k positions, without replacement |
random.shuffle(items) | Reorders the existing list; returns None |
Keep random.shuffle(cards) on its own line. Assigning its result back to cards loses the list binding by replacing it with None. Sampling positions is also different from requiring distinct values: random.sample([1, 1], 2) returns [1, 1].
Run a repeatable simulation
import random
sevens = 0
for _ in range(1000):
if random.randint(1, 6) + random.randint(1, 6) == 7:
sevens += 1
print(sevens)
Six of 36 equally likely pairs total 7, so the count is usually near 167, but it changes between runs. To reproduce a sequence for debugging, call random.seed(42) before generating the values, using the same Python version and call sequence. For passwords or security tokens, use secrets.
Find other everyday tools
statistics.mean([2, 4, 9]) returns 5; statistics.median([2, 4, 9]) returns 4. datetime.date.today() gives today's date. After import sys, sys.argv holds the script name followed by its arguments: running python greet.py Ada supplies ['greet.py', 'Ada'].
Key Takeaways
floor,ceil,intandroundanswer different rounding questions.- Use
math.isclosewhen comparing calculated floats. randintincludes both endpoints;randrangeexcludes its stop.sampleselects positions without replacement, so duplicate input values can repeat.shufflechanges a list in place and returnsNone.- A fixed seed helps reproduce random behavior; security tokens need
secrets.
1.3 Make a file reusable without starting its program
A function becomes easier to reuse when it lives in a focused module. Another program can import that function, and a test can check it without asking a person to run the whole application.
Split the function from its caller
Save this code as geometry.py:
"""Calculations for simple shapes."""
import math
def circle_area(radius):
"""Return the area of a circle with the given radius."""
return math.pi * radius ** 2
In the same folder, save this caller as main.py:
import geometry
print(geometry.circle_area(2))
Run python main.py. Python finds geometry, creates its function, and then the caller runs that function. The printed result is 12.566370614359172. Import without the .py ending. A valid module name uses letters, digits and underscores with a valid first character; my_tools.py works with import my_tools, while import my-tools is invalid syntax.
The strings at the starts of the module and function are docstrings. They describe the code's purpose and appear in help(geometry) and help(geometry.circle_area).
Understand what importing runs
On the first import, Python executes the module's top-level statements in order. A def statement creates a function; it does not execute that function's body. An unguarded print(circle_area(2)) at the bottom of geometry.py, however, would print during import.
That side effect matters when a module contains input prompts or a program loop. A test that only wants circle_area should not have to start the program and answer its prompts.
Guard the launch, keep the function available
Add this below the reusable function in geometry.py:
def main():
print(circle_area(2))
if __name__ == "__main__":
main()
Python sets __name__ for each module. A direct run, python geometry.py, sets it to "__main__", making the condition true and calling main(). Importing geometry sets it to "geometry", so Python skips the call. The definitions above the guard remain available in both cases.
Do not place circle_area itself inside the guard. An importer needs that definition even when the launch condition is false. The guard controls when the application starts; it does not prevent Python from loading the module.
Know where Python searches
Python checks built-in modules such as sys, then the folders in sys.path in order. Normally the running script's folder comes first in that path, followed by standard-library and installed-package folders. Keeping your own files beside the running script is a simple arrangement for a beginner project. A module outside the search path cannot be found by an ordinary import.
This order can also hide a library module. If you save your program as random.py, its import random can load your file instead of the library. A later random.randint(1, 6) then raises AttributeError. Rename the file, for example to dice.py; Python 3.13 and later may include a renaming hint in the error.
A loaded module is reused within the same run. In an interactive shell, editing the file and importing it again keeps the loaded version. Restart the shell before testing your changes.
Key Takeaways
- Import your own
.pyfile using its module name without the extension. - An import executes top-level statements, while function bodies wait for calls.
- Keep reusable definitions outside the
__main__guard and the program launch inside it. - A local file can hide a library module with the same name.
- Restart an interactive shell to test an edited module afresh.
1.4 Install packages for the Python you run
Third-party packages add code that does not come with Python. PyPI, the Python Package Index at https://pypi.org, hosts these projects. pip downloads and installs them for a particular Python installation.
Use the terminal, then use Python
A terminal command and a Python statement belong in different places. The >>> prompt means you are in Python's interactive shell. Entering pip install requests there gives SyntaxError; Python cannot read it as a statement.
In your terminal, run:
python -m pip install cowsay
Then put this in a Python program:
import cowsay
cowsay.cow("Hello, Ada")
The program prints a text cow beside a speech bubble containing the message. Installation makes the package available; the import creates the program's binding for it.
Match pip to your interpreter
The -m pip form asks the named Python to run its own pip. Use the command that starts your programs: often python3 on macOS or Linux, or py on Windows.
python3 -m pip install cowsay
python3 game.py
Both lines select the same Python command. If an installation reports success but a script still raises ModuleNotFoundError, compare the Python used to install with the one used to run the script. An editor may have selected a different interpreter from your terminal. Use the selected interpreter's pip before changing the import.
Inspect and maintain an installation
| Terminal command | Job |
|---|---|
python -m pip list | List installed packages and versions |
python -m pip show requests | Show details, including its installation location |
python -m pip install --upgrade requests | Update to the newest available version |
python -m pip install "requests==2.32.3" | Select that exact version |
python -m pip uninstall requests | Remove the package |
An exact version is useful when you need that particular release's behavior. show helps check which location pip actually changed.
Check the name in the documentation
The distribution name typed for pip can differ from the import name. Install beautifulsoup4, then use import bs4; install Pillow, then use import PIL. Repeatedly installing an import name is not a useful fix when the documentation names a different distribution.
Python's standard library, including math, random and unittest, already comes with Python. These modules do not require a pip installation.
Respect a managed installation
Some operating systems and package managers protect their Python with an externally-managed-environment error. This means a direct install is blocked to keep the managed installation consistent. Use a virtual environment, a separate Python and package folder for the project. Do not force the installation with sudo pip install.
Anyone can publish on PyPI. Before installing, check the exact spelling and read the project's page and documentation. Lookalike names can be typosquatting attempts. Prefer established, maintained packages, and install the ones your program actually needs.
Key Takeaways
- Run pip commands in a terminal and imports in Python code.
- Use
python -m pipwith the same interpreter command that runs the program. - Check the documentation for the import name; it may differ from the install name.
- Use pip to inspect, update, select a version or remove a package.
- A managed Python may require a project virtual environment.
- Check package spelling and documentation before installing third-party code.
2Read errors and tracebacks
Python's error output records both the operation that failed and the calls that reached it. Reading those clues in order helps you find a useful starting point instead of changing the first line you recognize. You will follow a traceback back to its input, distinguish common exception types, and recognize the quieter case in which a program runs normally but produces the wrong result. The expected behavior gives you something concrete to check.
2.1 Follow a traceback back to the bad input
A traceback tells you which calls were active when an unhandled exception stopped the program. It gives both a place to begin reading and a path back toward the value that caused the failure.
Start with the final line
Consider this complete program in scores.py:
def average(values):
return sum(values) / len(values)
def report(scores):
avg = average(scores)
print(f"Average: {avg:.1f}")
report([])
The final line of its error report is ZeroDivisionError: division by zero. This names the exception type and describes the failed operation. Start there, then read upward to the code location immediately above it.
A simplified traceback, with expression markers omitted, is:
Traceback (most recent call last):
File "scores.py", line 8, in <module>
report([])
File "scores.py", line 5, in report
avg = average(scores)
File "scores.py", line 2, in average
return sum(values) / len(values)
ZeroDivisionError: division by zero
Read each frame as an active call
A frame gives the file, line number and function, followed by the relevant code. in <module> means top-level code. The oldest call is at the top; the most recent is at the bottom. Reading from the bottom reverses that order so you can ask what failed before asking how execution got there.
- Line 2 divides by
len(values). For that divisor to be zero,valuesmust be empty. - Line 5 calls
average(scores), so inspect thescorespassed byreport. - Line 8 supplies
[], the empty list. That explains how the zero divisor reached the calculation.
The failing line and the cause are related, but they can be in different places. Replacing the division without deciding what an empty list should mean would hide the design question. Either the caller must avoid an empty list or the function needs a defined empty-input behavior.
Use highlights without depending on their exact shape
Python 3.11 and later can add ^ and ~ markers to show the failing part of an expression. They help when one line contains several operations. The exact highlighting varies between versions, but the call locations and exception message still give the reading order.
A traceback can contain frames from the standard library or an installed package. Find the lowest frame in your own code and inspect the arguments you supplied there. A library frame is a location clue; it is not evidence that the library contains the bug.
Recognize a syntax report
A syntax error in the script being started prevents that script from running. Python reports a file, line, marker and message without the usual traceback header. For example, if True is missing its colon; total = sum([1, 2, 3] leaves a parenthesis open.
The marked place is where Python noticed it could not parse the code. When that line looks valid, check the preceding line for an unclosed quote or parenthesis. There is no chain of runtime calls to follow when the starting script has not run.
An ordinary run stops at its first unhandled exception. Fix that problem and run again to expose anything later in the program.
Key Takeaways
- Read the exception type and message first, then the failing frame and its callers.
- Frames run from the oldest active call at the top to the newest at the bottom.
- Trace the bad value backward; its source can precede the crash.
- A syntax error in the starting script is reported before that script runs.
2.2 Tell a wrong name, type, value and result apart
An error's name narrows the search, and its message adds details about the values involved. A program can also be wrong without raising an exception, so checking its expected output remains part of finding errors.
Separate three kinds of problem
| Kind | What happens | Example |
|---|---|---|
| Syntax error | Python cannot parse the code | A missing colon after if |
| Runtime error | Execution reaches an invalid operation | Dividing by zero |
| Logic error | Execution finishes with the wrong behavior | A loop skips a price |
IndentationError is a kind of SyntaxError. A function body needs indentation; a missing indented block stops the code before it runs. Also inspect unclosed quotes or parentheses and misspelled keywords.
Read names and types closely
A NameError means the program does not know a name at that point. In prnt("hi"), the intended built-in is print. A name can also be used before assignment, or an import can be missing.
A TypeError means an operation received an unsuitable type or a call received the wrong number of arguments. "Age: " + 30 mixes a string and an integer. Write "Age: " + str(30) or f"Age: {30}". If area needs w and h, area(3) is also a TypeError, this time for a missing argument.
The message distinguishes these causes. In can only concatenate str (not "int") to str, Python names the types that met. After age = input("Age: "), age is text. age + 1 fails; for a whole-number entry, int(age) + 1 performs arithmetic.
A ValueError means the input's type is accepted, but its value is unsuitable. int("12.5") receives a string, which is an accepted input type, but the text does not represent a whole number in the required form. math.sqrt(-1) is another unsuitable value for a real-number calculation.
Check positions, keys and attributes
| Exception | Example | Cause |
|---|---|---|
IndexError | [4, 6, 10][3] | The last valid index is 2 |
KeyError | {"apples": 3}["pears"] | The dictionary lacks "pears" |
ZeroDivisionError | 10 / 0 | The divisor is zero |
AttributeError | math.sqroot(16) | The module lacks sqroot |
ModuleNotFoundError | import mathh | No module is found under that name |
ImportError | from math import square_root | The module lacks that imported name |
Zero divisors also fail with // and %. A missing list position is a different lookup from a missing dictionary key, even though both involve asking for something absent.
Detect a result that looks plausible
def total_price(prices):
total = 0
for i in range(1, len(prices)):
total += prices[i]
return total
For [4, 6, 10], this returns 16 instead of 20. There is no exception: indexes 1 and 2 exist, so every operation is legal. The loop starts too late and misses index 0. The error becomes visible when you compare the output with the intended total.
For an unfamiliar exception, look up its type and the general message in the official Python documentation. Remove your own paths and input values from a search; they rarely explain the general rule.
Key Takeaways
- Syntax errors prevent parsing; runtime errors occur during execution; logic errors give wrong behavior.
NameErrorconcerns a binding;TypeErrorconcerns a type or call arguments.ValueErrorconcerns an unsuitable value supplied in an accepted type.IndexError,KeyErrorandAttributeErrorrefer to different kinds of lookup.- Checking output against an expected result can reveal a logic error with no traceback.
3Find the cause of a bug
A wrong result becomes easier to investigate when you can reproduce it and say exactly how it differs from the expected result. You will turn that observation into a testable guess, narrow the search to a small piece of code, and inspect the values that matter. Prints, trace tables and debugger controls offer different ways to watch execution. After a focused fix, checking earlier working cases helps reveal any behavior the change affected.
3.1 Test a cause before changing the code
A bug is a difference between intended behavior and actual behavior. Debugging finds the cause and repairs it. A useful debugging session turns an observation into a testable explanation, then changes the code because of evidence.
Make the failure repeat
Record a specific input and the result it produces. For the price function that starts its loop at index 1, use [4, 6, 10]. The expected total is 20; the actual total is 16. These concrete values let you check whether each investigation still concerns the same bug.
The difference is 4, and the actual result equals 6 + 10. A specific hypothesis is that the first price is skipped. That hypothesis predicts something you can inspect: the loop will never visit index 0.
Follow six steps
- Reproduce: find an input that shows the bug reliably.
- State expected and actual results: calculate the expected result independently.
- Form a hypothesis: name a possible cause and its predicted behavior.
- Test it: inspect values with prints, a trace table or a debugger.
- Fix the cause: make one focused change.
- Retest: run the failing input and earlier working cases.
Print the index inside the loop. For this input, the only indexes are 1 and 2, confirming the prediction. Starting range at 0 fixes the skipped item. Iterating directly over the prices removes the need to manage an index:
def total_price(prices):
total = 0
for price in prices:
total += price
return total
The repaired function returns 20 for [4, 6, 10], 5 for [5] and 0 for []. The one-item case is useful because the buggy loop previously returned 0 for it.
Adding 4 to every answer would repair the observed total but leave the cause. For [5], that patch would return 4; the missing amount depends on the input. Check whether a proposed fix explains all the evidence before keeping it.
Narrow a larger search
Divide and conquer checks the values that feed the wrong result at a point partway through execution. If a needed value is already wrong, inspect the earlier code that produced it. If the needed values are still right, follow their later processing. Repeat inside the remaining section.
You can also call one function on its own or temporarily remove unrelated code. A minimal reproducible example keeps the shortest code and smallest input that still fail. For a problem first seen in hundreds of records, finding one or two records that trigger it makes every inspection easier.
Check what the data really is
A displayed 5 may be the string "5". "5" + "3" produces "53", while numeric addition gives 8. Inspect type(value) to check the type. Use repr(value) to reveal spaces and newline characters that an ordinary print can hide, such as ' Ada'.
Explain the code aloud, line by line, including what each loop actually visits and what each function returns. This is rubber duck debugging: explaining a step often exposes the gap between the intended operation and the written one.
Change one cause at a time so you can connect the result to the edit. Retest the cases that used to work as well as the one that failed.
Key Takeaways
- Reproduce the bug and record expected and actual results before editing.
- A useful hypothesis predicts something you can inspect.
- Fix the cause with one change, then retest failing and previously working cases.
- Use a minimal example or intermediate values to narrow the search.
- Check types and hidden characters when values look misleading.
3.2 Watch values change with prints and a debugger
A program's final answer hides the path that produced it. Temporary prints, a trace table and a debugger let you see that path and check a specific hypothesis about where it goes wrong.
Print a value at a known moment
Label diagnostic output so a number has a clear meaning. Python 3.8 and later support this f-string form:
print(f"{i=} {total=}")
For the buggy price loop, put it immediately before total += prices[i]. With prices [4, 6, 10], it prints:
i=1 total=0
i=2 total=6
These are totals before each addition. After the first print, the loop adds 6; after the second, it adds 10. A print after the addition would show different totals. Decide which moment answers your question before interpreting the output.
Trace the same code by hand
A trace table records variables step by step. For this loop:
Index i | Price added | Total before | Total after |
|---|---|---|---|
| 1 | 6 | 0 | 6 |
| 2 | 10 | 6 | 16 |
There is no iteration for index 0. Follow the actual start and stop values written in the code, even when you already know what the loop ought to do. A table helps separate what you expect from what happens.
Pause at a breakpoint
A debugger pauses a running program to let you inspect its current state. A breakpoint marks a place to pause. In editors such as Thonny and VS Code, click beside a line number and start debug mode; in IDLE, right-click the line and choose Set Breakpoint.
After pausing, choose a step based on the question you are investigating:
| Control | Effect |
|---|---|
| Step over | Run the current line, including a called function |
| Step into | Enter a function called on that line |
| Step out | Finish this function and pause after returning |
| Continue | Run until another breakpoint or the program ends |
If the caller passes correct arguments but receives a wrong answer, step into the function. If you only need to see the returned result, step over its call. Inspect the values before and after the line that might create the error.
Use Python's text debugger
Insert breakpoint() just before the code you want to inspect. With the default debugger configuration, running the program starts pdb at that point. Type these commands at its (Pdb) prompt:
p totaldisplaystotal.nsteps over the next line.ssteps into a function call.ccontinues execution.qquits the debugging session.
These are debugger commands, not lines to copy into your program.
Separate printed output from returned results
A function can print the right number and still return None because it lacks return total. Inspect the value received by the caller when the failure occurs after the function call. Seeing correct output inside the function does not establish that the caller received that number.
Use prints for quick evidence across loop iterations and the debugger to inspect a particular moment. After repairing the bug, remove temporary prints and breakpoint() calls so the finished program runs normally.
Key Takeaways
- Label debugging prints and note whether they run before or after an update.
- A trace table follows actual variable changes one step at a time.
- Breakpoints pause execution; stepping controls how much code runs next.
breakpoint()opens pdb by default;p,n,s,candqare debugger commands.- Inspect returned values when a function prints correctly but its caller fails.
- Remove temporary diagnostic output and breakpoints after the fix.
4Check your functions with tests
Automated checks let you repeat a useful example every time a function changes. You will write assertions with clear expectations, choose inputs that reach the edges of a rule, and keep tests from starting the application they import. The standard unittest framework then gives each test a name and a separate result. Reading its failures and errors connects testing to debugging: the test reproduces a problem, and investigation finds its cause.
4.1 Write checks that fail when an assumption is false
An assertion states something that must be true if your code is working correctly. It can stop a bug close to where it begins or compare a function's result with the value you expect.
Write a condition and an optional message
assert letter_grade(90) == "A", "90 should be an A"
Python evaluates the condition. If letter_grade(90) returns "A", the condition is true and execution continues without output. If it returns "B", Python raises AssertionError with the message 90 should be an A. The traceback points to the failing check.
Without the message, assert letter_grade(90) == "A" still checks the result. The final exception line contains AssertionError, while the traceback shows the expression. A useful message names the expectation that failed; it should help you locate the rule being checked.
Stop an internal mistake early
Suppose an internal calculation requires a nonempty list:
def average(values):
assert len(values) > 0, "values must not be empty"
return sum(values) / len(values)
An empty list now triggers the assertion before the division. The check documents the function's internal assumption and points directly to its violation. It does not give an empty list an average or decide how an application should respond to a user's missing data.
Keep user validation active
Python's -O option removes assert statements. A program that relies on an assertion to reject outside input can therefore behave differently when optimized. Check user input and other outside data with ordinary control flow:
def require_score(score):
if score < 0 or score > 100:
raise ValueError("score must be 0 to 100")
return score
Here raise ValueError(...) reports an unsuitable value. The if statement still runs under -O. Choose an assertion for an internal programming assumption and an active input check for data that the outside world can get wrong.
Avoid a check that can never fail
assert is a statement, not a function. Write the condition and message separated by a comma:
assert x > 10, "x too small"
Writing assert (x > 10, "x too small") creates a two-item tuple. A nonempty tuple is true even when its first item is False, so the assertion passes. Python can warn that this assertion is always true. Parentheses around just a condition, as in assert (x > 10), do not create that tuple; the comma inside the parentheses is the problem.
Use == for comparison. assert total = 20 is invalid syntax because = assigns a value.
Compare numbers with the intended precision
For a calculated float, use a closeness check:
import math
assert math.isclose(0.1 + 0.2, 0.3)
An exact comparison fails here because the stored float sum differs slightly from 0.3. A simple automated test applies the same assertion idea to a function: assert total_price([4, 6, 10]) == 20. The expected value comes from the function's rule, so the check can expose a wrong implementation.
Key Takeaways
assert condition, messageraisesAssertionErrorwhen its condition is false.- Assertions can check internal assumptions and expected function results.
- Use
ifand an error message or exception for outside input;-Oremoves assertions. - A comma inside
assert (condition, message)creates a truthy tuple. - Use
==for equality andmath.isclosefor calculated float comparisons.
4.2 Choose tests that can expose a bug
A unit test checks a small unit of code, usually one function, with a chosen input and an expected result. Automated tests make the same checks easy to repeat after edits, helping catch bugs that return in previously working behavior.
Start with the specification
Suppose a grading function must return "A" for scores of at least 90, "B" for scores of at least 80 but below 90, and "C" below 80. The buggy first condition is score > 90.
Tests should include several jobs:
- Typical cases check ordinary inputs inside each region, such as 95 and 85.
- Boundary cases check the values where a rule changes, such as 90 and 89, or 80 and 79.
- Edge cases check inputs such as zero or an empty collection when the function's rules allow them.
Work out the expected answer from those rules. Copying the current program's answer into a test can preserve its mistake.
Put the tests beside the module
Save tests in test_grade.py and the function in grade.py. The application launch in grade.py belongs under its __main__ guard, so importing the function does not start the program.
from grade import letter_grade
def test_letter_grade():
assert letter_grade(95) == "A"
assert letter_grade(90) == "A"
assert letter_grade(85) == "B"
For the buggy rule, the first assertion passes. The second fails because 90 is not greater than 90; execution stops there, before the third assertion in this function. Changing the first condition to score >= 90 repairs this boundary. Rerun all tests to check the affected case and the earlier behavior.
Make sure the tests actually run
Defining a test function does not call it. With no test runner, add test_letter_grade() at the bottom of the test file, then run python test_grade.py. To use pytest, install it with python -m pip install pytest, then run pytest test_grade.py in the terminal. It finds the test functions and reports their results. Keep manual calls out when letting a runner manage them.
A test that merely calls letter_grade(90) has no comparison. It can finish successfully while the returned answer is wrong; it fails only if the call raises an unexpected exception. An assertion connects the call to the required behavior.
Test returned values and random properties
A function that only prints a total returns None. A comparison such as assert print_total([4, 6]) == 10 therefore fails even if the screen displays 10. Have the calculation return its answer, then let main handle printing.
Random results need a different expectation. An unseeded die function cannot be required to return one exact face on every call. Check that each of 100 calls returns an allowed value from 1 through 6, or seed before a repeatable sequence. Passing the bounds test checks the bounds for those calls; it does not establish that every face is equally likely.
When a new bug appears, add a test that fails because of it before repairing the function. Keep that regression test so later edits check the same behavior again. Passing tests establish what happened for the tested cases; more inputs can still reveal another bug.
Key Takeaways
- Choose expected results from the specification, independently of the implementation.
- Test ordinary inputs, rule boundaries and relevant edge cases.
- Test functions need a call or a runner; definitions alone perform no checks.
- Assertions must compare returned results with the intended behavior.
- Test appropriate properties of random output or arrange a repeatable sequence.
- Keep a test for each newly found bug to help catch regressions.
4.3 Run and interpret a unittest test file
Python includes unittest, a test framework that discovers tests, runs them and reports separate outcomes. It needs no pip installation. Its class structure can be used as a template while you are learning to test functions.
Write independent test methods
For a function in grade.py, put this in test_grade.py:
import unittest
from grade import letter_grade
class TestLetterGrade(unittest.TestCase):
def test_top(self):
self.assertEqual(letter_grade(95), "A")
def test_boundary(self):
self.assertEqual(letter_grade(90), "A")
def test_low(self):
self.assertEqual(letter_grade(50), "C")
if __name__ == "__main__":
unittest.main()
TestLetterGrade inherits the test tools from unittest.TestCase. Each test is a method whose name starts with test. self identifies the current test object; keep it in each method's parameter list and before assertion method calls. You can copy this structure without designing classes of your own.
Tests run in alphabetical order of their method names by default, so test_boundary comes before test_low and test_top. Make every test supply its own needed input and work independently of previous tests.
Pick an assertion method
| Method | Check |
|---|---|
self.assertEqual(actual, expected) | Values are equal |
self.assertNotEqual(a, b) | Values differ |
self.assertTrue(condition) | A condition is true |
self.assertFalse(condition) | A condition is false |
self.assertIn(item, collection) | The item belongs to the collection |
self.assertAlmostEqual(a, b) | The difference rounds to zero at 7 decimal places by default |
Prefer assertEqual(a, b) to assertTrue(a == b) when comparing values: the failure report shows the two values. The unittest methods also keep their checks when Python runs under -O.
To check an expected exception, enclose the call in assertRaises:
def test_score_too_high(self):
with self.assertRaises(ValueError):
letter_grade(120)
This test requires the function's rule to reject that score with ValueError. A return without an exception fails this check; a different non-assertion exception is an error.
Run and read the report
Run python test_grade.py to execute its guarded unittest.main(). Or run python -m unittest for discovery of test*.py files in the current folder. python -m unittest -v prints individual test names and their outcomes.
In the short report, . means pass, F means an assertion failed and E means another exception occurred. For a grading function with score > 90, the three tests above produce F... The failure report for test_boundary shows 'B' != 'A': the actual result is "B", while the expected result is "A". The summary reports three tests and one failure.
Before editing the function, check the test's expectation against the grading rules. A wrong expectation needs a test fix. If no tests run, inspect the method names, the direct-run guard and the launch command. Also avoid naming your file unittest.py, which hides the standard module.
Key Takeaways
- Put
testmethods inside a class that inherits fromunittest.TestCase. - Use
self.assertEqualand other assertion methods for clear checks and reports. assertRaiseschecks an expected exception; float comparisons can useassertAlmostEqual.- Run a guarded test file directly or use
python -m unittestfor discovery. - Read
Fas a failed assertion andEas another exception. - Check a failed test against the specification before changing the function.
- Each test must run independently of alphabetical execution order.
Python Modules and Debugging examples
1Build a program from reusable pieces
20 steps
- 1IntroductionStart with code you can reuse
A program can call code from Python's standard library, another file you wrote, or an installed package. The first question is what name the program has available after importing.
You will follow a calculation from an import to its returned value, then arrange a reusable module and connect a package installation to its interpreter. Along the way, check whether a result is a value you keep or a change made to an existing list.
- 2ExplanationThe import chooses the call style
These imports expose the same function through different bindings:
import math print(math.sqrt(16))from math import sqrt print(sqrt(16))Both print
4.0. The first createsmath, so the call uses a dot. The second createssqrt, so it needs no module prefix. Using the wrong call style raisesNameErrorwhen that name has not been bound. - 3ExampleCalculate the hypotenuse
With
import math, a right triangle with sides 3 and 4 can use:length = math.sqrt(3**2 + 4**2)The squares are 9 and 16. Their sum is 25, and
math.sqrt(25)returns5.0. Python evaluates the argument before calling the function.The prefix also tells a reader where
sqrtcame from. A wildcard import hides that origin and can replace existing names, such as a variable namede. - 4ChallengeFollow the alias binding
A fresh script runs these lines. What happens on the final line, and how can it be repaired?
import statistics as st print(statistics.mean([4, 8, 12]))---reveal---
It raises
NameErrorbecause the import bindsst, notstatistics. Usest.mean([4, 8, 12]), which returns8. An alias selects the binding in this program; it does not also create the original module name. - 5ExampleRound for enough whole containers
A wall needs paint for 23 square meters. Each can covers 10 square meters.
import math cans = math.ceil(23 / 10)Division gives
2.3;ceilgives3, enough whole cans.round(2.3)gives only2.For negative inputs, down still means toward negative infinity:
math.floor(-2.5)is-3, whileint(-2.5)cuts toward zero and gives-2. - 6Key InsightMatch the comparison to the number
Calculated floats can differ slightly from their decimal-looking targets:
0.1 + 0.2 == 0.3 # False math.isclose(0.1 + 0.2, 0.3) # TrueUse closeness for that approximate comparison. Separately, a function's allowed inputs still matter:
math.sqrt(-1)raisesValueError.The built-in
roundneeds no import. At an exact halfway value it chooses the even neighbor:round(2.5)is2, andround(3.5)is4. - 7ExplanationPick the random result you need
After
import random, choose a call by its job:randint(1, 6)picks an integer including 6.randrange(1, 6)picks an integer excluding 6.random()gives a float from 0.0 up to, but excluding, 1.0.choice(items)picks one item.sample(items, k)pickskpositions without replacement.
Two selected positions can hold the same value. Sampling
[1, 1]at both positions therefore gives repeated 1s. - 8ChallengeKeep the shuffled list
What value does
nameshold at the end, and how should the last line change to keep the list?names = ["Mina", "Luis", "Jo"] names = random.shuffle(names)---reveal---
It holds
None.shufflechanges the existing list, then returnsNone; the assignment replaces the list binding with that return value. Userandom.shuffle(names)on its own line. The list's new order is random, but its return behavior is fixed. - 9ConnectionRepeat a simulation and find other tools
Call
random.seed(42)before generating values to repeat the same sequence on the same Python version. That makes a random failure easier to reproduce. Usesecretsfor security tokens.Other standard modules solve everyday jobs:
statistics.mean([2, 4, 9])gives5, andmediangives4.datetime.date.today()gives today's date.sys.argvlists the running script's name followed by its command-line arguments. - 10ExamplePut a reusable function in a module
Save this as
geometry.py:import math def circle_area(radius): """Return the circle's area.""" return math.pi * radius ** 2A neighboring script can run
import geometry, thenprint(geometry.circle_area(2)). It prints12.566370614359172.Import the file without
.py. The function's first string is its docstring, whichhelp(geometry.circle_area)can display. - 11ExplanationAn import executes top-level statements
Suppose
geometry.pyends withprint(circle_area(2)), outside a function. Its first import prints the area as a side effect.Python runs module-level statements in order. Defining
circle_areacreates the function without running its body; calling it inside the top-level print runs the calculation. A second import in the same process reuses the loaded module. An interactive shell also keeps that loaded version after the file is edited, until you restart it. - 12ExampleLaunch only during a direct run
Leave the reusable function above this block:
def main(): print(circle_area(2)) if __name__ == "__main__": main()A direct run sets
__name__to"__main__", somain()runs. An import sets it to"geometry", so the launch is skipped.The definitions stay available in either case. This lets a test import
circle_areawithout producing the program's ordinary output. - 13ChallengeKeep the function available to an importer
A file defines
convertinside itsif __name__ == "__main__":block. A fresh test process imports the file and cannot findconvert. What arrangement fixes both reuse and the direct-run launch?---reveal---
Move
def convert(...)outside the guard and keep only the program launch inside it. During import the guard is false, so a definition inside it is skipped. Moving the definition makes it available without starting the application. - 14TipCheck the file that an import finds
Python checks built-in modules, then searches
sys.pathin order. The script's folder normally appears before library folders. A localrandom.pycan therefore hide therandomyou intended to import.Rename that file to something such as
dice.py. For your own helpers, use valid names such asmy_tools.pyand keep them beside the beginner script that imports them. A file outside the search path is not found by an ordinary import. - 15ExampleInstall in the terminal, import in Python
In the terminal, run:
python -m pip install cowsayThen Python code can use:
import cowsay cowsay.cow("Hello, Ada")The package prints a text cow and speech bubble.
pipinstalls code, whileimportcreates the binding used by your program. The installation command is not a Python statement; at a>>>prompt it produces invalid syntax. - 16ExplanationUse pip belonging to the running Python
If a script runs with
python3, install through the same command:python3 -m pip install cowsay python3 game.py-m pipselects pip belonging to that interpreter. On Windows, your command may instead bepy.If the editor cannot import a successfully installed package, compare its selected interpreter with the one used in the terminal. Installation success in one Python does not install the package into every other Python.
- 17ChallengeSeparate the install name from the import name
A package's documentation says to install
Pillowand importPIL. The install succeeds for your running Python, butimport Pillowfails. What should change, and why?---reveal---
Use
import PIL. The distribution name used by pip can differ from the Python import name it provides. Since the interpreter match is already established, repeating the unchanged installation command would leave this particular naming mistake in place. - 18TipInspect a package before changing it
python -m pip listshows installed versions.show requestsgives one package's version and location;install --upgrade requestsupdates it, anduninstall requestsremoves it. Each short command followspython -m pip.Check the exact PyPI name and project documentation before installing. For an
externally-managed-environmenterror, use a project virtual environment; do not force an installation withsudo pip install. - 19ChallengePredict an import and a later call
A fresh module
sizes.pycontains this code. Another script runsfrom sizes import batches, thenprint(batches(17)). What prints?import math def batches(n): return math.ceil(n / 5) if __name__ == "__main__": print(batches(12))---reveal---
Only
4prints. Import creates the function but skips the guarded print. The later call divides 17 by 5, then rounds 3.4 up to 4. Themathbinding insidesizesis available to its function. - 20RecapFollow code from its source to the call
Choose an import that matches the names you want to use. Check what a library call returns, including a possible
Nonefrom an operation that changes a list.Keep reusable definitions available during import and place the application launch behind the main guard. For installed code, match pip to the running interpreter and check the documented import name. When a call fails, these details help separate a missing binding, a missing attribute and a missing installation.
2Read a failure back to its cause
11 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionUse the report as evidence
- 2ExampleAn empty list reaches a division
- 3ExplanationFind the most recent frame
- 4ExampleFollow the argument back through its callers
- 5ChallengeLocate an unsuitable argument
- 6ExplanationDistinguish grammar, types and values
- 7ExplanationDifferent lookups have different errors
- 8ExampleA wrong total can finish normally
- 9ChallengeExplain a failed addition
- 10ChallengeUse a library frame without blaming the library
- 11RecapChoose the next place to look
3Investigate one bug from evidence to repair
14 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionMake the wrong result repeat
- 2ExampleState expected and actual results
- 3ExplanationMake the guess testable
- 4ExamplePrint before the update
- 5ExampleTrace the additions by hand
- 6ChallengeInterpret a print after the update
- 7ExampleRepair the iteration itself
- 8ConnectionRetest inputs with different shapes
- 9TipShrink a larger failure
- 10ChallengeCheck a value that looks numeric
- 11ExplanationPause at the call you need to inspect
- 12ChallengeChoose the stepping command
- 13ChallengeInvestigate a correct print and a wrong return
- 14RecapKeep the repair tied to evidence
4Turn a boundary bug into a lasting test
16 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionGive expected behavior a repeatable check
- 2ExplanationAn assertion states an expectation
- 3ChallengeFind a check that cannot fail
- 4Key InsightKeep outside-input checks active
- 5ExampleChoose inputs around the grade change
- 6ExampleWrite a test function that imports the code
- 7TipExecute the checks you defined
- 8ChallengeRepair a threshold and preserve the check
- 9ConnectionTest the returned result or a valid property
- 10ExampleUse the unittest class template
- 11ExplanationPick the assertion for the outcome
- 12ExampleRead a failed grade check
- 13ChallengeDistinguish a failure from an error
- 14TipCheck what discovery actually ran
- 15ChallengeUse a report to plan the repair
- 16RecapKeep a test for the behavior you repaired
Python Modules and Debugging practice questions
Set 1: Imports, error clues and first checks10 Practice Questions
Question 1 of 10
import math before its calculation. How should it call the square-root function on 81?Show answerHide answer
math, so the function is reached through math.sqrt(81), which returns 9.0. Calling sqrt(81) alone would raise NameError because this import did not create a separate binding for sqrt.Question 2 of 10
math.ceil(35 / 8) return, and what does it represent?Show answerHide answer
4.375, so four shuttles leave three people without seats. ceil rounds up to the next integer, giving five shuttles. Rounding to the nearest integer would give four here, but the requirement is enough whole shuttles for everyone.Question 3 of 10
text_tools.py defines shorten. How can another file in the same folder import and call it?Show answerHide answer
.py extension, so import text_tools creates the module binding. Dot notation then reaches its shorten function. Including .py in the import treats the ending as part of an import path instead of a file extension.Question 4 of 10
>>> prompt and enter pip install cowsay. Why does Python report invalid syntax?Show answerHide answer
>>> prompt accepts Python statements, while pip installation syntax is a terminal command. Leave the Python shell and run python -m pip install cowsay in the terminal. Importing first cannot make a not-yet-installed package available.Question 5 of 10
ZeroDivisionError. Its frames, top to bottom, show show([]), mean(data), and sum(values) / len(values). What chain best explains the failure?Show answerHide answer
len(values), and the earlier calls trace that value back to the empty list. Its length is zero, causing the exception. A zero numerator is allowed when the divisor is nonzero, so the sum alone does not explain this crash.Question 6 of 10
int("7.25") raise?Show answerHide answer
int, but "7.25" is not text in the whole-number form that this conversion requires. That makes it a ValueError. A TypeError would indicate an unsuitable input type or operation, not this unsuitable string value.Question 7 of 10
[9, 2, 5] as 16 but returns 7. You suspect it skips the first item. What observation would most directly test that idea?Show answerHide answer
Question 8 of 10
What two lines are printed?
total = 0
for n in [3, 8]:
print(f"{n=} {total=}")
total += n
Show answerHide answer
Question 9 of 10
What happens when this code runs normally?
count = 6
assert count < 5, "too many"
print("saved")
Show answerHide answer
6 < 5 is false, so the assertion raises AssertionError with the supplied message. Execution stops before the final print because the exception is unhandled. The message is part of the exception; it is not an ordinary print followed by continued execution.Question 10 of 10
The rule is free through weight 12, a fee of 4 for 13 through 18, and a fee of 7 above 18. Tests for 5 and 20 pass. What results and edit correctly diagnose this code?
def fee(weight):
if weight < 12:
return 0
if weight <= 18:
return 4
return 7
Show answerHide answer
Set 2: Modules, debugging choices and test results10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
import statistics as st, the next line is statistics.mean([3, 7]). No other imports or assignments appear. What explains the error?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What is the value of tickets after this code?
import random
tickets = [11, 22, 33]
tickets = random.shuffle(tickets)
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
double and then has the unguarded line print(double(6)). Another script imports it twice in one run. How many times does that line print?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
python -m pip show cowsay reports a location in Python B, and the editor cannot import cowsay. What should you do first?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
This function should add every quantity. What kind of problem explains the result of total([2, 5, 8])?
def total(values):
result = 0
for i in range(1, len(values)):
result += values[i]
return result
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
assert (score >= 0, "negative score"). It passes when score is -3. What change both repairs the check and keeps validation active under -O?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
test_discount, which contains two assertions, but never calls it. You run that file with python test_discount.py; there is no test runner code. Why are no assertions checked?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
self.assertEqual(lookup("x"), 7). The call to lookup raises an unexpected KeyError before returning. What short result marks this test?Show answerHide answer
Save this lesson to see this answer.
Set 3: Trace causes across modules and tests10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
What does this code print?
from math import sqrt as root
sqrt = 4
result = root(sqrt ** 2)
print(result, sqrt)
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
random.sample(["blue", "blue", "red"], 2). Why can the result contain two "blue" values?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
A module contains this code. A fresh process imports it and then calls helper.answer(). What happens?
# helper.py
if __name__ == "__main__":
def answer():
return 42
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
run_report() call, then your parse(raw) call, and ends inside a library conversion with ValueError. You inspect raw at your parse call and find "9x", although the rule requires whole-number text. Where should the investigation go next?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
result + 2, where result is the call's return value. Why does a TypeError occur?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
answer = calculate(7). You want to inspect the first line inside calculate. What control should you use?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
charge(6) returns 3, but its test expects 3 and passes. What change creates a useful regression test and fixes the rule?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
test_alpha expecting 4 but receiving 5, test_beta passing, and test_gamma raising an unexpected TypeError. What compact progress string does default alphabetical execution produce?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
unittest.TestCase has check_empty containing a failing equality check and test_full containing a passing one. The runner reports one test and OK. How should you make the missing behavior check run?Show answerHide answer
Save this lesson to see this answer.
Set 4: Combine import, debugging and testing decisions10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
assert total == 10 for an internal calculation and assert age >= 0 for user input. It runs under -O with total equal to 9 and age equal to -1. What happens to those checks, and what change keeps the required input validation?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
random.randint(2, 5) 50 times. A new test seeds to the same value on the same Python version and repeats the same calls. What should its checks expect?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
beautifulsoup4 as the distribution and bs4 as its module. A script imports bs4 successfully. A coworker wants to change python -m pip install beautifulsoup4 to install bs4 so the names match. Is matching those names a sound reason for that change?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
display.py reports these calls, top to bottom: start(" "), read_count(text), then int(text), ending in ValueError. What sequence best tests the likely cause before a fix?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
[6, 3, 2]. What expected and actual results for two new inputs expose the remaining problem?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
This function prints a correct product, but its caller gets a TypeError in area(4, 5) + 1. Where should you inspect, and what result explains the failure?
def area(w, h):
product = w * h
print(product)
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What happens under an ordinary, unoptimized run?
import math
assert math.isclose(0.1 + 0.2, 0.3)
assert 0.1 + 0.2 == 0.3
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
pick() returns a random integer from 3 through 7, including both endpoints. pair_total() adds two calls to pick(). A test asserting that every unseeded total equals 10 sometimes fails even though both functions work correctly. What per-result check matches the required total range?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
reserve(n) must accept whole-number counts from 0 through 8 and raise ValueError outside that range. Tests using assertRaises(ValueError) report F for -1 because no exception occurs, and E for 9 because a TypeError occurs. A test at 2 passes. What behavior should the repair provide?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
random.py and imports random, then fails because random.randint is missing. What first change addresses the name clash?Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Alias
as for an imported module or object; the import binds that name in the importing program.stats in import statistics as stats lets the script call stats.mean([1, 3, 8]).Assertion
AssertionError when false.assert count == 3 checks an internal result against the count you independently expect.AssertionError
"wrong total" directs attention to the test that compared the calculated total.B
Boundary case
Breakpoint
Bug
C
Call chain
report function to an average function shows how a list reached the function that divided by its length.Ceiling
math.ceil gives this upward integer rounding.D
Debugger
calculate to inspect its arguments when the caller receives a wrong answer.Debugging
Distribution
beautifulsoup4 is installed by that name, but its documentation tells Python users to import bs4.Divide and conquer
Docstring
help().circle_area can explain the radius argument and the area returned to its caller.Dot notation
math.sqrt(25) finds the sqrt function inside the already imported math module.E
Edge case
Exception
average before its caller prints the calculated mean.F
Floor
math.floor moves toward negative infinity when needed.math.floor(-3.2) differs from conversion with int(-3.2).Frame
H
Hypothesis
I
ImportError
from requests a name that is unavailable in a module, even though Python found the module itself.from math import square_root points to the requested name, not an absent installation.In-place operation
random.shuffle(cards) changes cards and returns None, so it needs no assignment.IndexError
names[4] suggests checking the list's length and the loop bounds that produced 4.Input validation
if score < 0 remains active when Python runs with assertions removed.Interpreter
K
KeyError
"price" can reveal that the current dictionary never received the expected price entry.L
Logic error
M
Main guard
if __name__ == "__main__": block that runs its body for direct execution and skips it during an ordinary import.main() lets tests import the module's functions without launching its application.Minimal reproducible example
Module
.py file, whose functions and variables can be imported for use in another program.math module supplies sqrt, so a program can calculate square roots without defining that function.Module search path
sys.path that Python checks after its built-in modules when finding imports.ModuleNotFoundError when the script imports it.Module shadowing
random.py can leave a script without the library's randint function.N
NameError
sqrt after only import math means the function needs the math. prefix there.O
Optimize option
-O changes Python execution by removing assert statements, so required validation cannot rely on those statements.P
Package
pip
python3 -m pip show cowsay asks pip associated with python3 to show that package's installation details.Print debugging
print(f"{i=} {total=}") labels the loop index and total at the inspected moment.Pseudo-random number
random.randint(1, 6) can represent a die result in a board-game simulation.PyPI
R
Regression
Runtime error
S
Sampling without replacement
["A", "A", "B"] can select both positions that contain "A".Seed
Specification
Standard library
math for calculations and random for simulations such as rolling dice.Step into
result = convert(text) when the returned value is wrong and the input text looks correct.Step over
Syntax error
if ready can be fixed by supplying the colon required before its indented block.T
Test discovery
test*.py file pattern.python -m unittest can run matching files without listing each one in a command.Test error
E can result when a function raises TypeError before the equality check receives a value.Test failure
F can show that the function returned "B" where the checked rule required "A".Test method
test, allowing unittest to discover and execute its checks.test_limit can check a boundary separately from the test for an ordinary input.TestCase
unittest.TestCase can call self.assertEqual inside its named test methods.Top-level code
print can produce output during import even when no function is called by the importer.Trace table
Traceback
TypeError
"Score: " + 8 identifies a string and integer that cannot be joined by that expression.Typosquatting
U
Unit test
V
ValueError
int("6.2") means the string does not describe a whole number in the required form.W
Wildcard import
* to bring a module's public names into the current program, which can obscure origins and replace existing bindings.from math import * can replace a variable called e with the mathematical constant.About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.