Skip to main content
Editor

Program Design and Testing: Plan, Build and Debug Code

Beginner35 min3 exercises35 XP
0/3 exercises

Every app on your phone began as an idea that people turned into a working program by planning it, building it in small steps, and testing it until it did what its users needed.

That app is a computing innovation: something new that depends on a program to do its job. Some computing innovations are physical objects, like a robot vacuum or a fitness band. Some are software, like a video editor. Others are ideas that only exist because of computing, like video streaming or online banking.

Why do teams build better programs?

Most real software is built by teams. A team whose members bring different talents and backgrounds makes a better product, because each person notices things the others miss. One teammate is good at layout, another knows the code best, and a third uses a screen reader every day and spots every button that has no label.

Different perspectives also help a team avoid bias. Picture a voice-controlled homework helper tested only by people who speak with the same accent as its developers. It could work perfectly for them and fail for half the school. A team that includes different voices, or that asks different users for feedback, catches the problem before launch.

That is why developers consult the people who will use their program. What users say in conversations, and what developers see them do, shows the purpose of the program from many points of view, and the team can then build something that serves all of those users.

Online tools let a team work together even when its members are not in the same room. Shared documents let everyone write a plan together and comment on each other's ideas. Code-sharing sites such as GitHub let programmers see each other's changes, review them, and suggest fixes.

A common way for two people to work together is pair programming. One person, the driver, types the code. The other, the navigator, reviews each line as it is written, thinks ahead, and spots mistakes. The two swap roles often, so both of them understand the whole program.

Effective teams also practice people skills:

  • Communication: saying clearly what you are working on, and listening to others.
  • Consensus building: working toward a decision everyone can support.
  • Conflict resolution: settling disagreements about the work without attacking the person.
  • Negotiation: finding a compromise, such as trading a feature you wanted for one your teammate cares about.
  • What is a program for, and how does it work?

    Every computing innovation has a purpose. It either solves a problem, like a bus-tracking app that tells you when to leave home, or lets people pursue an interest through creative expression, like a beat-making app. Developers who understand the purpose clearly build a better innovation, because they can check every feature against it.

    A program is a collection of statements that performs a specific task when a computer runs it. Programs are also called software. A code segment is a smaller collection of statements that is part of a program, such as one function or one loop.

    A program has to work for many different inputs and situations, not only the one its author tried first. A calculator that adds 2 + 2 correctly but crashes on 0.5 + 0.25 is not finished.

    The behavior of a program is what it does while it runs, and it is usually described by how a user interacts with it. You can describe a program at two levels of detail:

  • Broadly, by what it does: "The app shows how many minutes until the next bus."
  • In detail, by what it does and how its statements do it: "Every 30 seconds it downloads the bus's location, calculates the distance to your stop, divides that by the bus's average speed, and displays the result in minutes."
  • Where do inputs come from, and where do outputs go?

    Input is data sent to a computer for a program to process. Output is any data a program sends to a device, such as a screen, a speaker or a motor. Both come in many forms:

    FormInput exampleOutput example
    Tactile (touch)tapping a screen, pressing a controller buttona game controller vibrating
    Audiospeaking to a voice assistanta song playing
    Visuala phone camera scanning a QR codea map drawn on the screen
    Texttyping a searcha text message

    Input can come from a person or from another program. A weather app gets its forecast from a weather service's program, not from you.

    Inputs usually affect the output. Output also depends on the program's prior state, the values it has stored. A game that shows "New high score!" compares your score (input) with the best score it saved earlier (state).

    Many programs spend most of their time waiting for events. An event is tied to an action, such as a key press, a mouse click, a tap, the program starting, or any other action the program is set up to respond to, and it supplies input data to the program. In event-driven programming, statements run when an event triggers them, instead of running in order from top to bottom. The "jump" code in a game sits idle until you press the jump button.

    The demo below fakes a stream of events with a list, so you can watch each event trigger its own handler. Notice that the same tap event produces different output depending on the state.

    A tiny event-driven game
    # Each event triggers its own handler function
    state = {"score": 0, "playing": False}
    def on_start():
    state["playing"] = True
    print("Game started")
    def on_tap():
    if state["playing"]:
    state["score"] += 10
    print(f"Tap! Score is now {state['score']}")
    else:
    print("Tap ignored: press start first")
    def on_quit():
    state["playing"] = False
    print(f"Game over. Final score: {state['score']}")
    handlers = {"start": on_start, "tap": on_tap, "quit": on_quit}
    # In a real app these events come from the screen. Here they are a list.
    events = ["tap", "start", "tap", "tap", "quit"]
    for event in events:
    handlers[event]()
    Output
    Tap ignored: press start first
    Game started
    Tap! Score is now 10
    Tap! Score is now 20
    Game over. Final score: 20
    Exercise 1: Handle Volume Button Events
    Write Code

    A music app starts at volume 5. Write final_volume(events) that takes a list of button events and returns the volume after all of them have been handled:

  • "up" raises the volume by 1, but never above 10.
  • "down" lowers it by 1, but never below 0.
  • "mute" sets it to 0.
  • final_volume(["up", "up", "down"])  ->  6
    def final_volume(events):
    volume = 5
    # Handle each event here
    return volume

    How do programmers plan and build a program?

    There is more than one way to develop a program. Some developers follow an ordered, planned process; others explore, trying ideas to see what works. Most development processes include these phases:

  • Investigating and reflecting: learning what the problem is and who has it.
  • Designing: planning how the program will work.
  • Prototyping: building a rough version that works.
  • Testing: checking that it does what it should.
  • An iterative process goes around these phases again and again. After each round of feedback, testing or reflection, the team refines and revises the program, and that sometimes means going back to an earlier phase. If testers find the bus app's countdown confusing, the team returns to designing.

    An incremental process breaks the problem into smaller pieces and makes sure each piece works before adding it to the whole. You might build the part that downloads bus locations and test it, then add the distance calculation and test again, and so on. Many teams do both: they build in pieces and keep improving each one.

    How do you find out what a program needs to do?

    Designing a program starts with investigation to find its requirements. Investigation uncovers the program's constraints, the limits it must work within ("it has to run on old phones", "it must be ready by May"), as well as the concerns and interests of the people who will use it. Common ways to investigate:

  • Surveys sent to many possible users
  • User testing, where people try a prototype while you watch
  • Interviews with a few users, in depth
  • Direct observation of people doing the task the way they do it today
  • Requirements describe how the program must function, and they often include the interactions it must offer users, such as "a student can see their next three classes with one tap." A program's specification is the document that defines its requirements.

    The design phase plans how to meet the specification. It may include brainstorming, planning and storyboarding the screens, organizing the program into modules and functions, drawing diagrams of the user interface layout, and planning how the program will be tested, all before much code is written.

    Check your understanding

    Two students are pair programming on a quiz app. Which description matches how pair programming works?

    Answers

    A team is designing an app that helps students sign up for after-school tutoring. Which two activities are ways to investigate what the app must do? Select two answers.

    Select 2 answers.

    Answers

    A student builds a weather app by first getting the part that downloads today's temperature to work, then adding a five-day forecast, then adding storm alerts. Each part is tested before the next one is added. Which approach does this describe?

    Answers

    How do you document code and give credit?

    Program documentation is a written description of what a code segment, event, procedure or program does and how it was developed. Comments are documentation written into the program itself for people to read. The computer ignores them, so they never change how the program runs. In Python a comment starts with #, and a string placed right under a def line, called a docstring, describes the function.

    Document your program throughout development, not only at the end. Notes written along the way help you keep a program correct as it changes, and they help teammates understand code they did not write. Some programming environments, including some block-based ones, do not support comments. In those, you document in another way, such as a separate document or a design journal.

    Any code you did not write yourself needs an acknowledgement. That includes code written with a partner, starter code from your teacher, code from a library or an open-source project, and code produced by an AI assistant. The acknowledgement can go in the program's documentation, usually as a comment, and it should name where the code came from or who wrote it.

    Documentation and credit in comments
    def ticket_price(age):
    """Return the school-play ticket price in dollars for a person's age.
    Requirements from interviews with the ticket sellers (Oct 2026):
    under 12 pays $5, 12 to 64 pays $10, 65 and over pays $6.
    """
    # The age bands were written with my lab partner, Jordan Lee,
    # while pair programming.
    if age < 12:
    return 5
    if age < 65:
    return 10
    return 6
    # Credit: this table loop was suggested by an AI assistant (ChatGPT).
    # I tested it and changed the column widths.
    for age in [8, 30, 70]:
    print(f"Age {age:>3} | ${ticket_price(age)}")
    # The docstring is documentation that Python keeps with the function
    print(ticket_price.__doc__.splitlines()[0])
    Output
    Age   8 | $5
    Age  30 | $10
    Age  70 | $6
    Return the school-play ticket price in dollars for a person's age.

    What kinds of errors can a program have?

    There are four kinds of errors to recognize:

  • A syntax error is a mistake where the rules of the programming language are not followed, like a missing parenthesis or a misspelled keyword. Python will not start a program that has a syntax error anywhere in it.
  • A run-time error happens while the program is running, such as dividing by zero or asking for item 10 of a 3-item list. Each programming language defines its own run-time errors; Python gives them names like ZeroDivisionError and IndexError.
  • A logic error is a mistake in the algorithm or program that makes it behave incorrectly or unexpectedly. The program usually runs without crashing and gives a wrong answer, which makes logic errors the hardest to notice.
  • An overflow error happens when a computer tries to handle a number outside the range of values it can store. Python's whole numbers can grow as large as memory allows, but its decimal numbers (floats) have a fixed size, so a big enough number overflows.
  • The demo triggers one of each. try and except catch each error so the demo keeps going; read more about error messages in Python Error Messages.

    One error of each kind
    broken_line = 'print("Score:" 10)'
    try:
    compile(broken_line, "demo", "exec") # check the rules without running it
    except SyntaxError as error:
    print(f"Syntax error: {error.msg}")
    scores = [90, 85, 77]
    try:
    print(scores[3])
    except IndexError as error:
    print(f"Run-time error: {error}")
    try:
    print(float(10 ** 400))
    except OverflowError as error:
    print(f"Overflow error: {error}")
    def average(values):
    return sum(values) / len(values) + 1 # the + 1 does not belong here
    print(f"Logic error: average([80, 90]) returned {average([80, 90])}, not 85.0")
    Output
    Syntax error: invalid syntax. Perhaps you forgot a comma?
    Run-time error: list index out of range
    Overflow error: int too large to convert to float
    Logic error: average([80, 90]) returned 86.0, not 85.0

    How do you find and fix a bug?

    Effective ways to find and correct errors:

  • Test cases: run the program on chosen inputs and compare its output with the output you expected.
  • Hand tracing: follow the code line by line on paper, writing down the value of each variable as it changes.
  • Visualizations: tools that draw a program's variables as it runs, like this site's Python visualizer.
  • Debuggers: tools that pause a program at a line you choose so you can inspect its variables.
  • Extra output statements: temporary print() calls that show what is happening inside the program.
  • Testing uses defined inputs to check that a program produces the expected outcomes, and the results tell you what to revise. You need the requirements to choose those inputs, because the requirements say what the expected outcomes are. The ticket function above follows the drama club's requirements for its school play: under 12 pays $5, ages 12 to 64 pay $10, and 65 and over pay $6. Those numbers show where bugs like to hide: at the boundaries, where the price changes. Strong tests check both sides of each boundary (11 and 12, 64 and 65). They also check the extremes of the input, the youngest and oldest ages the app accepts, and values one step beyond those extremes, which the app should reject.

    Here is a version of ticket_price with a bug in it. The tests find it.

    Test cases find a boundary bug
    def ticket_price(age):
    if age < 12:
    return 5
    if age <= 65: # the bug is on this line
    return 10
    return 6
    # (age, expected price) pairs taken from the requirements
    test_cases = [(0, 5), (11, 5), (12, 10), (64, 10), (65, 6), (120, 6)]
    for age, expected in test_cases:
    actual = ticket_price(age)
    result = "pass" if actual == expected else "FAIL"
    print(f"age {age:>3}: expected ${expected:<2} got ${actual:<2} {result}")
    Output
    age   0: expected $5  got $5  pass
    age  11: expected $5  got $5  pass
    age  12: expected $10 got $10 pass
    age  64: expected $10 got $10 pass
    age  65: expected $6  got $10 FAIL
    age 120: expected $6  got $6  pass

    Only the test at exactly 65 fails. Someone who tested ages 30 and 80 would have shipped this bug. After fixing a failing test, run every test again, because a fix can break something that worked before.

    When a test fails and you cannot see why, add extra output. The function below should count the scores of 70 or more. It returns 2 for a list that has three passing scores, and the extra print() shows why.

    Extra output reveals an off-by-one error
    def count_passing(scores):
    count = 0
    for i in range(1, len(scores)):
    print(f" checking index {i}: {scores[i]}") # extra output for debugging
    if scores[i] >= 70:
    count += 1
    return count
    result = count_passing([95, 60, 70, 88])
    print(f"Result: {result} (expected 3)")
    Output
      checking index 1: 60
      checking index 2: 70
      checking index 3: 88
    Result: 2 (expected 3)

    The output shows the loop starting at index 1, so the first score, 95, is never checked. This is an off-by-one error: a logic error where a loop runs one time too few or one time too many. Changing range(1, len(scores)) to range(len(scores)) fixes it. Delete the extra print() once the bug is gone.

    Practice: fix a bug, then write tests

    Exercise 2: Fix the Off-by-One Bug
    Fix the Bug

    A reading-log app counts how many pages you read. Reading from page 10 to page 12 means you read pages 10, 11 and 12, which is 3 pages. The function below returns 2 instead. Find the off-by-one error and fix it.

    pages_read(10, 12)  ->  3
    pages_read(5, 5)    ->  1
    def pages_read(first_page, last_page):
    count = 0
    page = first_page
    while page < last_page:
    count += 1
    page += 1
    return count
    Exercise 3: Write Tests That Catch the Bug
    Write Code

    The drama club's requirements: under 12 pays $5, ages 12 to 64 pay $10, and 65 and over pay $6.

    Write price_tests() so it returns a list of test cases, where each test case is a tuple (age, expected_price). The checker runs your tests against a correct ticket_price and against buggy versions that each have one boundary in the wrong place. Your tests pass when:

    1. you have at least 4 test cases and every expected price matches the requirements, and

    2. every buggy version fails at least one of your tests.

    def price_tests():
    # Each test case is (age, expected price in dollars)
    return [
    (30, 10),
    (80, 6),
    ]

    Check your understanding

    Mia's game uses a collision-detection procedure that she copied from an open-source project. What is the best way for her to acknowledge it?

    Answers

    The code segment below is meant to display the sum of the values in scores. What happens when it runs?

    scores ← [88, 92, 75]
    total ← 0
    i ← 0
    REPEAT 3 TIMES
    {
    total ← total + scores[i]
    i ← i + 1
    }
    DISPLAY(total)
    Answers

    The procedure countAtLeast is supposed to return how many values in numList are greater than or equal to target. Which procedure call would reveal that it contains an error?

    PROCEDURE countAtLeast(numList, target)
    {
    count ← 0
    FOR EACH num IN numList
    {
    IF(num > target)
    {
    count ← count + 1
    }
    }
    RETURN(count)
    }
    Answers

    A program should accept a quantity from 1 to 10, inclusive, and reject anything else. The tester has already tried 1, 5 and 10. Which two additional inputs best check the behavior one step beyond the extremes of the input? Select two answers.

    Select 2 answers.

    Answers

    A video game stores a player's coins in a value that can hold whole numbers from 0 to 65,535. A player with 65,535 coins picks up one more, and the coin counter shows 0. Which kind of error is this?

    Answers

    Exam topics: 1.1 Collaboration (CRD-1.A, CRD-1.B, CRD-1.C), 1.2 Program Function and Purpose (CRD-2.A, CRD-2.B, CRD-2.C, CRD-2.D), 1.3 Program Design and Development (CRD-2.E, CRD-2.F, CRD-2.G, CRD-2.H), 1.4 Identifying and Correcting Errors (CRD-2.I, CRD-2.J)

    Related Tutorials

    Was this tutorial helpful?