COMPUTER PROGRAMMING 1 (PYTHON) • PROBLEM SOLVING & MINI-PROJECTS

Testing Programs — I can test a program by trying edge cases and fixing UI/logic bugs at my level.

Learn to break your own code on purpose so your users never have to.

Historical Context & Motivation

Writing code is only half the battle — the other half is making sure it actually works. Since the dawn of computing, programmers have dealt with bugs, a term popularized when engineers found a literal moth jammed inside a relay of the Harvard Mark II computer in 1947. That small insect became a symbol of a much larger truth: every program will contain errors, and finding them requires a disciplined, intentional process called software testing.

In the early days of programming, testing was informal — programmers would simply run their code and hope for the best. As software grew more complex and the consequences of failure became more severe (think rocket launches, banking systems, and medical devices), the industry developed rigorous testing methods. Today, professional developers spend roughly 30–50% of their total project time on testing. Understanding how to test effectively is one of the most valuable skills you can build, even at the introductory level.

1947
The First "Bug"
Grace Hopper's team logs a real moth found in the Mark II relay, coining the popular use of "bug" and "debugging" in computing.
1970s
Structured Testing Emerges
Glenford Myers publishes The Art of Software Testing, establishing key ideas like boundary analysis and systematic test design.
1999
Unit Testing Goes Mainstream
Kent Beck releases the JUnit testing framework, sparking the test-driven development (TDD) movement where tests are written before code.
2010s
Continuous Integration & Automation
Automated testing pipelines become standard. Tools like pytest for Python make it easy for even beginners to write and run tests.
Today
Testing as a Core Skill
Industry surveys show that companies consider testing ability a top hiring criterion. AI-assisted testing tools are expanding what one developer can verify.

The central question this lesson addresses is straightforward but powerful: how do you gain confidence that your program actually does what you intended? The answer lies in thinking creatively about inputs, deliberately trying to break your own code, and methodically fixing the problems you uncover.

Core Principles of Testing

Effective testing is not random button-mashing — it is guided by a set of foundational principles that help you choose which inputs to try and where to look for problems. These principles apply whether you are testing a simple calculator script or a full-fledged web application.

1

Edge Cases

Edge cases are inputs at the extreme boundaries of what a program should handle — the smallest, largest, emptiest, or most unusual values. If your function accepts ages, edge cases include 0, negative numbers, and extremely large numbers like 999.
2

Normal Cases

Normal cases (also called "happy path" tests) use typical, expected inputs. For an age function, a normal case might be 16 or 35. These verify your code works under ordinary conditions.
3

Logic Bugs

A logic bug occurs when the program runs without crashing but produces the wrong result. For example, using < instead of <= in a comparison, causing an off-by-one error.
4

UI Bugs

A UI bug (user interface bug) means the program's output is confusing, misleading, or incorrectly formatted — even if the underlying calculation is correct. A misspelled label or missing units are common examples.
5

Regression Testing

Regression testing means re-running previous tests after you fix a bug to ensure your fix did not accidentally break something else. Every fix is a potential source of new bugs.
✦ KEY TAKEAWAY
Think of testing like proofreading an essay. You would not only re-read the introduction — you would check the conclusion, every paragraph transition, and especially the tricky sentences where you were unsure of grammar. In the same way, you should test the boundaries and tricky spots in your code, not just the parts you feel confident about. The goal is to find problems before your users do.

Visual Explanation — The Testing Workflow

The diagram below illustrates the complete testing workflow you should follow when developing a Python program. It starts with writing code, moves through identifying the kinds of test cases you need, running those tests, and then looping back to fix any bugs you discover. Notice that the process is cyclical — you keep testing and fixing until you are satisfied that the program handles all important scenarios.

The testing workflow forms a loop: write code → identify test cases (normal and edge) → run tests → check results. If a test fails, identify whether the bug is a logic bug or a UI bug, fix it, and loop back to re-test.

As you follow this diagram, notice that steps 2 through 6 may repeat many times for a single program. Professional developers often cycle through this loop dozens of times before releasing software. At your level, even two or three cycles will dramatically improve your code quality. The key insight is that testing is not something you do once at the end — it is woven into the development process from the start.

How Testing Works in Python

In Python, you can test your code using several strategies. The simplest approach — and the one you should master first — is manual testing with print statements and direct function calls. As you progress, you can adopt assert statements and eventually formal testing frameworks like pytest. This section focuses on the first two approaches since they are immediately useful in your current projects.

Strategy 1: Manual Testing with Print Statements

The most intuitive way to test is to call your function with specific inputs and print the output to see if it matches your expectations. For example, if you write a function is_even(n) that returns True when n is even, you would test it like this:

🖥️ Manual Test Example
print(is_even(4)) # Expected: True print(is_even(7)) # Expected: False print(is_even(0)) # Expected: True (edge case!) print(is_even(-2)) # Expected: True (edge case!)

Strategy 2: Assert Statements

An assert statement is a one-line test built into Python. It checks whether a condition is True. If the condition is False, Python immediately raises an AssertionError, telling you exactly which test failed. The syntax is:

ASSERT SYNTAX
assert condition, "error message"
condition — a boolean expression you expect to be True. error message — optional text displayed when the assertion fails, helping you find the problem quickly.
✅ Assert Statement Example
assert is_even(4) == True, "4 should be even" assert is_even(7) == False, "7 should be odd" assert is_even(0) == True, "0 should be even" assert is_even(-3) == False, "-3 should be odd" If all assertions pass, Python runs silently — silence means success.

Choosing Your Test Inputs

You cannot test every possible input (there are infinitely many), so you must be strategic. A good test suite covers three categories of input: normal cases that represent typical usage, edge cases at the boundaries of valid input, and invalid inputs that should be handled gracefully (like entering a word when a number is expected). Even five to ten well-chosen tests can catch the vast majority of bugs in a typical student project.

Types of Bugs & Edge Cases

Not all bugs are created equal. Understanding the different categories helps you know what to look for and how to fix each type. The diagram below classifies the most common bugs you will encounter in introductory Python programs, along with the kinds of edge cases that expose them.

The three main bug categories: syntax errors (caught by Python automatically), logic bugs (wrong results), and UI bugs (confusing output). Edge cases at the bottom are the inputs most likely to reveal logic and UI bugs.
Test case categories applied to a letter-grade function
Input TypeExample for a "letter grade" functionWhy It Matters
Normal casegrade(85) → "B"Verifies the basic logic works for typical inputs.
Boundarygrade(90) → "A" or "B"?Tests whether boundary values (90, 80, 70) are handled by ≥ or > comparisons.
Zerograde(0) → "F"Zero is a common edge case that can trigger unexpected behavior.
Negativegrade(-5) → error or "F"?Tests whether the function handles invalid negative scores gracefully.
Over maximumgrade(105) → "A" or error?Checks for scores above the expected max (like extra credit).

Worked Example — Testing & Fixing a Grade Calculator

Let's walk through a complete example. Suppose you have written a function that converts a numeric score (0–100) into a letter grade. The initial code has two bugs — one logic bug and one UI bug. We will find and fix both using systematic testing.

📝 Original (Buggy) Code
def letter_grade(score): if score > 90: return "A" elif score > 80: return "B" elif score > 70: return "C" elif score > 60: return "D" else: return "you failed"
Testing & Debugging letter_grade()
1
Step 1 — Test Normal CasesStart with typical values. We call letter_grade(95) and get "A" — correct. We call letter_grade(85) and get "B" — correct. We call letter_grade(50) and get "you failed" — the logic may be right but the output is unprofessional.
Normal cases mostly pass, but the "F" output is a UI bug — it should say "F", not "you failed".
2
Step 2 — Test Boundary / Edge CasesNow we test the exact boundaries. We call letter_grade(90) expecting "A" but we get "B". This reveals a logic bug: the condition uses > 90 (strictly greater than) instead of >= 90 (greater than or equal to). A student who scores exactly 90 should receive an "A".
Bug found: > should be >= in all conditions — this is an off-by-one logic bug.
3
Step 3 — Fix Bug #1 (Logic Bug)Change every > to >= in the conditions: if score >= 90:, elif score >= 80:, and so on. Now letter_grade(90) correctly returns "A".
Logic bug fixed. Boundary tests now pass.
4
Step 4 — Fix Bug #2 (UI Bug)Change return "you failed" to return "F" in the else branch. The output should be consistent — all other grades are single letters, so "F" should follow the same pattern.
UI bug fixed. Output is now consistent and professional.
5
Step 5 — Regression TestRe-run all previous tests: letter_grade(95) → "A" ✓, letter_grade(90) → "A" ✓, letter_grade(85) → "B" ✓, letter_grade(50) → "F" ✓. All tests pass — the fixes did not introduce new bugs.
All tests pass. Debugging complete!
✅ Fixed Code
def letter_grade(score): if score >= 90: # Fixed: >= instead of > return "A" elif score >= 80: return "B" elif score >= 70: return "C" elif score >= 60: return "D" else: return "F" # Fixed: "F" instead of "you failed"

Comparing Testing Strategies

There are several ways to test your Python code, each with distinct strengths and limitations. As a beginning programmer, you will primarily use manual testing and assert statements, but it helps to understand where these fit in the bigger picture of professional software development.

Comparison of testing strategies available to introductory Python programmers
StrategyStrengthsLimitations
Print & CheckEasy to learn. No setup required. Good for quick checks during development.You must manually read every output. Easy to miss errors. Not reusable — you have to re-type tests each time.
Assert StatementsAutomatic pass/fail. Self-documenting. Can be left in code for regression testing.Stops at the first failure, so you only see one bug at a time. No detailed reporting.
pytest FrameworkRuns all tests even if some fail. Rich error messages. Industry-standard tool.Requires installation and learning a new tool. Overkill for very small programs.
Peer TestingAnother person may think of inputs you overlooked. Catches UI bugs you are blind to.Requires coordination. Results depend on the tester's skill and patience.
✦ KEY TAKEAWAY
Choosing a testing strategy is like choosing how to check your math homework. You can re-read your work (print & check), use the answer key on a few problems (asserts), or ask a friend to review it (peer testing). The best approach is usually a combination — start with print statements to explore, then lock in your findings with assert statements so you can easily re-run your tests whenever you change the code.

Connection to Professional Testing

The manual and assert-based testing you are learning now is the foundation of everything professionals do — they have simply built more powerful tools on top of these same ideas. Understanding how your current skills connect to professional practices will help you see where your learning journey is headed.

How introductory testing skills map to professional software development practices
What You Do NowProfessional EquivalentKey Difference
Print statements to check outputUnit tests — automated functions that test one piece of codeUnit tests run automatically every time code changes, catching regressions instantly.
Trying edge cases by handBoundary value analysis — formal method for choosing test inputsProfessionals use mathematical models to guarantee they test every boundary.
Asking a friend to test your programQA (Quality Assurance) teams — dedicated testersQA testers follow detailed test plans and use specialized tools to simulate thousands of users.
Re-running tests after a fixCI/CD pipelines — servers that run all tests automaticallyEvery code change triggers hundreds of automated tests in seconds — no human involved.

As you advance in your programming studies, you will encounter tools like pytest, unittest, and eventually continuous integration platforms like GitHub Actions. For now, the most important skill is the mindset: always ask yourself "what inputs could break this?" before you consider your program finished. That habit will serve you in every programming language and framework you ever use.

Practice Problems

PROBLEM 1 — CONCEPTUAL
What is the difference between a logic bug and a UI bug? Give one example of each in a program that calculates the area of a rectangle.
PROBLEM 2 — BASIC CALCULATION
Consider this function: def discount_price(price, percent_off): return price - price * percent_off A user calls discount_price(100, 20) expecting to get 80.0 (a 20% discount on $100) but gets −1900.0 instead. Identify the bug and write the corrected line of code.
PROBLEM 3 — INTERMEDIATE
You write a function that checks if a year is a leap year: def is_leap_year(year): if year % 4 == 0: return True else: return False List three edge-case test inputs you should try, explain what the expected output should be for each, and identify whether this function has any bugs for those inputs.
PROBLEM 4 — APPLIED
You are building a simple password checker that prints whether a password is "strong" or "weak." A strong password must be at least 8 characters long and contain at least one digit. Write the function, then write at least four assert statements that test normal cases, edge cases, and potential UI bugs.
PROBLEM 5 — CRITICAL THINKING
A classmate argues: "I ran my program once and it gave the right answer, so it must be correct." Construct a specific example of a function that gives the correct answer for one typical input but fails for a specific edge case. Use this example to explain why a single successful test is not sufficient evidence that a program is bug-free.

Lesson Summary

Testing is the practice of intentionally trying inputs to verify that your program behaves correctly. Effective testing requires you to think beyond normal cases and deliberately explore edge cases — inputs at the boundaries such as zero, negative numbers, empty strings, and values at cutoff thresholds. When a test reveals a problem, you classify it as either a logic bug (wrong result) or a UI bug (confusing or poorly formatted output), then fix it and run regression tests to make sure your fix didn't break anything else.

You can test Python code using print statements for quick exploration and assert statements for reusable, automatic pass/fail checks. The testing mindset — always asking "what input could break this?" — is the single most important habit you can build. It connects directly to professional practices like unit testing and continuous integration, and it will make you a more confident, effective programmer at every stage of your career.

Varsity Tutors • Computer Programming 1 (Python) • Testing Programs — I can test a program by trying edge cases and fixing UI/logic bugs at my level.