Chapter: Program Testing and Debugging

Hello! Welcome to one of the most important topics in programming: Testing and Debugging. Think of yourself as a detective. You've written a program (the 'case'), but is it working perfectly? Does it have any hidden problems ('clues')? In this chapter, you'll learn how to be a great code detective! We'll explore how to find and fix errors (called 'bugs') in your programs. This skill is super important because almost no program works perfectly on the first try. Let's learn how to make our programs reliable and correct!


The Three Pesky Villains: Types of Program Errors

In the world of programming, an error is called a bug. Your job is to find these bugs and fix them. Bugs usually come in three main flavours. Understanding them is the first step to defeating them!

1. Syntax Errors

This is the most common and easiest type of error to fix. A syntax error is like making a grammar mistake when writing in English. The programming language has strict grammar rules, and if you break them, the computer gets confused and can't understand your instructions.

Analogy: Imagine writing "Cat the on mat sat the." in English. It has all the right words, but the grammar is wrong, so it doesn't make sense. A syntax error is the same thing for a computer.

  • Common Causes: Misspelling a command (like typing 'prnt' instead of 'print'), missing brackets `()`, forgetting a comma `,`, or incorrect indentation.
  • How to Find Them: Don't worry, the computer will find these for you! When you try to run the program, the compiler or interpreter will stop and give you an error message, often telling you the exact line number where the mistake is.
2. Run-time Errors

A run-time error is a bit trickier. It happens when your program is running (at "run-time"). The grammar (syntax) is perfect, but you've asked the computer to do something impossible. The program starts running but then crashes halfway through.

Analogy: You give someone a perfectly grammatical instruction: "Divide this pizza among zero people." The instruction itself is clear, but the action is logically impossible to perform.

  • Common Causes: Trying to divide a number by zero, trying to open a file that doesn't exist, or trying to access an item in a list that isn't there (e.g., asking for the 10th item in a list that only has 5 items).
  • How to Find Them: The program will crash and usually display an error message that explains what went wrong, like "DivisionByZeroError". Thorough testing helps you discover these before users do!
3. Logic Errors

These are the sneakiest and most difficult errors to find! With a logic error, your program's syntax is correct, and it runs without crashing. However, it produces the wrong result. The computer is doing exactly what you told it to do, but what you told it to do was wrong.

Analogy: You're following a recipe perfectly, but the recipe mistakenly tells you to add 1 cup of salt instead of 1 cup of sugar. You won't know there's a problem until you taste the final cake and it's horrible!

  • Common Causes: Using the wrong mathematical operator (e.g., `>` instead of `<`), getting a formula wrong (e.g., calculating area instead of perimeter), or a mistake in the flow of the program.
  • How to Find Them: These are tough because the computer doesn't give you any error messages. You have to find them by carefully testing your program with well-chosen data and seeing if the output is what you expect. If it's not, you'll need your detective tools, which we'll cover later!
A Quick Look at Special Numerical Errors

Sometimes, we run into errors specific to handling numbers:

Overflow Error: This happens when a number is too BIG to be stored in a variable. Imagine trying to pour a 2-litre bottle of soda into a small coffee cup!

Underflow Error: This is the opposite. It happens when a number is too small (too close to zero) for the computer to store accurately.

Rounding & Truncation Errors: These are small inaccuracies that occur when dealing with decimal numbers. A rounding error happens from rounding numbers up or down, while a truncation error happens when digits are simply chopped off.


Key Takeaway: The Three Error Types

Syntax Error: Grammar mistake. The program won't run at all.
Run-time Error: Impossible instruction. The program crashes while running.
Logic Error: Flawed thinking. The program runs but gives the wrong answer.




Becoming a Detective: The Art of Program Testing

You can't fix bugs if you don't know they exist! Testing is the process of running your program with specific inputs to see if it behaves as expected and to find any errors. The goal is to be tough on your own program and try to break it!

Designing Good Test Data

A critical part of testing is choosing the right data to test with. You should check if the program's own data validation (its ability to check if input data is sensible) is working correctly. We can split our test data into three groups.

Let's use an example: A program that checks if a person's age (valid range: 18 to 65) makes them eligible for a standard work position.

1. Normal Data

This is sensible, everyday data that you expect the program to handle correctly.

Example: For our age checker, normal data would be ages like 25, 40, and 60. The program should say "Eligible".

2. Extreme (Boundary) Data

This is one of the most important types of test data! Boundary cases are values at the very edges of the allowed range. Bugs often hide at these boundaries.

Example: For the 18-65 range, the boundaries are 18 and 65. You should also test the numbers just outside the boundary, like 17 and 66.
- Input `18` -> Expected Output: "Eligible"
- Input `65` -> Expected Output: "Eligible"
- Input `17` -> Expected Output: "Not Eligible"
- Input `66` -> Expected Output: "Not Eligible"

3. Erroneous (Invalid) Data

This is data that the program should reject. You are testing if your program can handle bad inputs gracefully without crashing.

Example: For our age checker, erroneous data would be values like -5, 200, or even text like "Hello". The program should respond with a clear error message, like "Invalid Age", and not crash.


Key Takeaway: Testing with Purpose

To test properly, use a mix of data:
Normal: The expected, easy inputs.
Boundary: The edge cases (e.g., min/max values). This is where bugs love to hide!
Erroneous: The bad inputs that your program should reject.




The Debugging Toolkit: Finding and Fixing Bugs

Okay, your testing has revealed a bug. Now it's time for debugging – the process of finding the exact cause of the bug and fixing it. Don't worry if this seems tricky at first; it's a skill you build with practice!

Manual Debugging: The Dry Run

A dry run is when you pretend to be the computer. You read your code line by line and track the values of all your variables in a trace table. This is an incredibly powerful way to find logic errors.

Example: Let's trace a small program that is supposed to calculate `3 + 2 + 1`.

Code:
Line 1: total = 0
Line 2: number = 3
Line 3: WHILE number > 1
Line 4:    total = total + number
Line 5:    number = number - 1

Trace Table:

Line Number number total Condition (number > 1) Notes
1 ? 0 - `total` is initialised.
2 3 0 - `number` is initialised.
3 3 0 True Loop starts.
4 3 0 + 3 = 3 - `total` is updated.
5 3 - 1 = 2 3 - `number` is updated.
3 2 3 True Loop continues.
4 2 3 + 2 = 5 - `total` is updated.
5 2 - 1 = 1 5 - `number` is updated.
3 1 5 False Loop ends. The final `total` is 5, not 6! We found a logic error! The condition should be `number > 0`.
Software Debugging Tools

Modern programming environments come with powerful tools to help you debug:

Breakpoints: This is like a "pause" button for your code. You can set a breakpoint on a specific line. When the program reaches that line, it pauses, allowing you to inspect the values of all variables at that exact moment. It's great for seeing where things start to go wrong.

Program Trace (or Watch): This feature lets you "watch" a variable. As the program runs, the debugger will show you the value of that variable in real-time, or list every change made to it. It's like a live trace table!

Flags: A simple but effective trick you can do yourself. A flag is just a message you print to the screen to see if your program reaches a certain point. For example, you could add a line `print("I am inside the loop now!")` to confirm that your loop is actually running.

Stubs: When writing large, modular programs, you might need to test one part that depends on another part you haven't written yet. A stub is a placeholder function that returns a simple, predictable value. For example, if you need a function `calculateAverage()` but haven't written it, you can create a stub that just `return 10` so you can test the rest of your code.


Key Takeaway: The Debugger's Toolkit

Trace Table: Manually track variables to understand your code's flow.
Breakpoints: Pause your code at a specific line to inspect everything.
Watch: See how a variable's value changes as the program runs.




More Than One Way to Code: Comparing Solutions

Often, there are many different ways to write a program to solve the same problem. A good programmer thinks about which solution is the "best". But what makes one solution better than another?

We usually compare solutions based on two main things:

1. Steps of Operation (Efficiency)

This refers to how many steps or operations the computer has to perform to get the job done. Fewer steps usually mean a faster, more efficient program.

Analogy: Imagine finding a name in a dictionary.
- Solution A (Inefficient): Start at the first page and read every single name until you find the one you want. This could take a very long time!
- Solution B (Efficient): Open the dictionary to the middle. If your name comes before, you search the first half. If it comes after, you search the second half. You keep repeating this. It's much, much faster!

2. Resource Usage (Memory)

This refers to how much computer memory (RAM) your program needs while it's running. An efficient solution uses as little memory as necessary.

Analogy: Imagine giving someone directions to your house.
- Solution A (High Resource Usage): Give them a huge, detailed printed map of the entire city. It has all the information but is bulky and uses a lot of paper.
- Solution B (Low Resource Usage): Give them simple, turn-by-turn instructions. It's lightweight and only contains the essential information.

Example: Sum of Numbers from 1 to N

Let's say we want to find the sum of all numbers from 1 to 100.

Solution A (Looping):

total = 0
FOR i = 1 TO 100
  total = total + i
NEXT i

This solution is easy to understand, but it takes about 100 steps (100 additions).

Solution B (Mathematical Formula):

$$Total = N \times (N + 1) / 2$$

total = 100 * (100 + 1) / 2

This solution uses a clever formula. It only takes one calculation (a multiplication, an addition, and a division). It is far more efficient in terms of steps!


Key Takeaway: Better Code

A "better" program is often one that is more efficient. When comparing solutions, ask yourself:
1. How fast is it? (Fewer steps of operation)
2. How much memory does it use? (Lower resource usage)