Testing, Debugging, Exceptions, and Assertions Cheat Sheet
High-quality code is achieved through rigorous testing, systematic debugging, and proactive handling of exceptions and assertions to ensure program correctness and robustness.
Core Principles
- Defensive Programming: Write specifications for functions, modularize programs, and check conditions on inputs/outputs (assertions) to build resilient code.
- Testing/Validation: Compare input/output pairs to specifications to identify and fix issues.
- Debugging: Systematically study events leading up to an error to understand and resolve the problem.
- Assertions: Use `assert` statements to verify assumptions about the program's state and halt execution if these assumptions are violated, serving as a form of defensive programming.
Action Steps
- 1. Design code from the start to ease testing and debugging by breaking it into modules.
- 2. Document constraints and assumptions for each module.
- 3. Ensure code runs without syntax or static semantic errors before testing.
- 4. Define a set of expected results, including input sets and their corresponding outputs.
- 5. Study events leading up to an error to understand 'why' it's happening.
- 6. Use print statements to test hypotheses about code behavior.
- 7. Implement `try-except` blocks to handle potential exceptions gracefully.
- 8. Use `assert` statements to check assumptions and ensure program integrity.
Key Terms
- Defensive Programming: Writing code that anticipates and handles potential errors or unexpected inputs to prevent failures.
- Testing/Validation: The process of comparing actual input/output pairs against the specified behavior of a program.
- Debugging: The systematic process of identifying, analyzing, and removing errors (bugs) in software.
- Unit Testing: Validating each individual piece or function of a program separately.
- Regression Testing: Adding tests to catch bugs as they are found and to ensure that previously fixed errors are not reintroduced.
- Integration Testing: Testing to ensure that different modules or components of a program work together correctly.
- Black Box Testing: Testing a program's functionality based on its specifications, without knowledge of its internal code structure.
- Glass Box Testing: Testing a program's internal structure and code paths to ensure all parts of the code are exercised.
- Exception: An unexpected condition that occurs during program execution, deviating from the normal flow.
- Handler: A block of code (e.g., `except` clause) designed to catch and manage specific exceptions.
- Assertion: A statement used to check assumptions about the program's state, raising an `AssertionError` if the assumption is false.
Pro Tips
- Modularize programs to test and debug individual components independently.
- Use black box testing to explore paths through the specification without looking at the code, which can be reused if implementation changes.
- Employ glass box testing to use code directly to guide test case design, aiming for path-completeness.
- When debugging, ask 'How did I get the unexpected result?' instead of 'What is wrong?'
- Use the scientific method for debugging: study data, form a hypothesis, conduct repeatable experiments, and test with the simplest input.
- Handle specific exceptions using separate `except` clauses for more targeted error management.
- Use assertions to check types of arguments, invariants on data structures, constraints on return values, and violations of procedure constraints.
Pitfalls to Avoid
- Skipping testing and debugging leads to undetected bugs and unreliable software.
- Rushing integration testing can result in an overall program that doesn't function correctly.
- Relying solely on glass box testing can lead to missing bugs if not all paths are covered or if loops are not tested exhaustively.
- Failing silently when an error occurs is a bad practice as the user receives no warning.
- Forgetting to close parentheses, quotations, or other syntax elements results in `SyntaxError`.
Myth vs Reality
- A path-complete test suite guarantees no bugs.: A path-complete test suite can still miss bugs, as demonstrated by the `abs(-1)` example where the function incorrectly returns -1 despite a path-complete suite.
- Debugging is about finding what is wrong.: Debugging is about understanding how an unexpected result was obtained by studying the events leading up to it.
Real World Examples
- Making soup with bugs falling from the ceiling.: Illustrates testing (checking soup for bugs), defensive programming (keeping the lid closed), and eliminating the source of bugs (cleaning the kitchen).
- Calculating the average of grades for students, where some students may have no grades.: Demonstrates handling `ZeroDivisionError` when calculating the average for a student with an empty grade list, either by flagging the error or assigning a default value (e.g., 0.0).
- Using `assert len(grades) != 0` in an average function.: Ensures the function halts immediately with an `AssertionError` if given an empty list, preventing division by zero and clearly indicating a problem with the input data.
Related cheat sheets
More like this