0% found this document useful (0 votes)
21 views39 pages

G3 Computing Textbook Chapter 06

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views39 pages

G3 Computing Textbook Chapter 06

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 39

CHAPTER

06 Testing and Debugging

235
236
236
6.1 Bugs and Debugging

When a program does not behave as intended by the programmer, the program is said to have a bug. The
process of fixing the program so that it behaves as intended is called debugging.

U
DID YO
N O W ?
K
Figure 6.1 shows a journal entry dated
9 September 1947 by users of an
early electronic computer called the
“Mark II”. They had discovered a moth
trapped in the computer, causing it to
malfunction. This dead moth is often
considered the first actual computer
“bug” to be found.

Figure 6.1 A moth trapped in a computer was the


first actual computer “bug”

ER MS
KEY T
Bug
A defect in a program that causes it to behave in an unintended manner

Debugging
The process of identifying and removing defects from a program

6.2 Types of Program Errors

LEARNING OUTCOMES
2.4.8 Understand and describe types of program errors: syntax, logic and run-time; and explain
why they occur.

237
There are three main types of program errors that cause bugs:

Syntax errors Logic errors Run-time errors


Errors caused by incorrect Errors that cause the Errors that occur while a
source code that violate program to not behave as program is running
the rules of the language expected

Example Example Example


Spelling mistakes, Use of a flawed or Incorrect use of
including case incomplete algorithm commands (e.g.,
sensitivity (e.g., eilf (e.g., inappropriate dividing by zero, using
instead of elif, If sequencing of an invalid list index)
instead of if) code, incorrect loop Input data that has not
Incorrect indentation condition, algorithm been properly validated
Invalid variable names that does not consider (e.g., trying to convert
(e.g., starting a variable all possible cases) a str of letters entered
name with a number, by the user into an int)
using reserved words) Conditions outside of
Incorrect sequence of the program’s control
symbols in source code (e.g., running out of
(e.g., extra right bracket memory)
without matching left
bracket, missing colon
after condition of while
loop)

Figure 6.2 Description of program error types

Note that the three types of program errors are not mutually exclusive. For instance, some run-time errors
may also be considered logic errors.

6.2.1 Syntax Errors

Syntax errors are usually the easiest to detect as they are “caught” at the initial stage before the code can
even run. It is usually not necessary to design test cases to find syntax errors.

Let us look at a program to find the average value in a list of numbers. In the program in Figure 6.3, we
introduced a syntax error on line 5 by using the assignment operator (=) instead of the equality operator
(==).

238
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Figure 6.3 A syntax error is introduced on line 5 (highlighted)

By fixing this syntax error, the program is able to run successfully.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Enter integer, blank to end: 2

Enter integer, blank to end: 0

Enter integer, blank to end: 2

Enter integer, blank to end: 4

Enter integer, blank to end:

2.0

Figure 6.4 The syntax error on line 5 is fixed by using the correct operator (highlighted)

239
6.2.2 Logic Errors

While syntax errors are usually obvious when they occur, logic errors may be “hidden” and may not show
any obvious sign that something is amiss. In such cases, the only way to detect the logic error is by running
a test case (see section 6.3) and observing that the actual output of the program is different from the
expected output.

For example, in Figure 6.5, we have introduced a logic error on line 12 by using an incorrect formula that
multiplies (instead of dividing) sum_values by the number of items in values to calculate the average.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Enter integer, blank to end: 2

Enter integer, blank to end: 0

Enter integer, blank to end: 2

Enter integer, blank to end: 4

Enter integer, blank to end:

32

Figure 6.5 Example of a logic error

While the program appears to finish running without errors, the output value of 32 is different from the
expected output of 2.0 based on the input data of 2, 0, 2 and 4. Such unexpected or incorrect output is the
most obvious sign that a program contains “hidden” logic errors.

240
6.2.3 Run-Time Errors

A run-time error refers to an error that occurs while a program is running.

For example, for the program in Figure 6.4, when the input is “four” instead of the digit “4”, a run-time error
occurs. This run-time error is caused by not applying a format check on the input data to ensure that it is a
valid integer (i.e., numeric digits only with a possible minus sign in front).

The program gives an error message as shown below.

Enter integer, blank to end: 2


Enter integer, blank to end: 0
Enter integer, blank to end: 2
Enter integer, blank to end: four

ValueError Traceback (most recent call last)


Input In [1], in <cell line: 4>()
5 if input_str == "":
6 break
----> 7 values += [int(input_str)]
9 # Process
10 if len(values) > 0:

ValueError: invalid literal for int() with base 10: 'four'

Figure 6.6 Example of a run-time error in Python

In Figure 6.7, we have removed any checks for an empty list before calculating the average. When we
run this program and immediately enter a blank, the program crashes with a division by zero error. This is
another example of a run-time error.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

Enter integer, blank to end:

241
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Input In [1], in <cell line: 11>()
9 # Process
10 sum_values = sum(values)
---> 11 average = sum_values / len(values)
13 # Output
14 print(average)

ZeroDivisionError: division by zero

Figure 6.7 The checks for an empty list are removed

Run-time errors may be caused by logic errors. However, the run-time error may not occur immediately on
the line of code containing the logic error. Such cases are tricky because the programmer would usually
need to “work backwards” from the location of the crash to find out where the logic error is actually located
(see section 6.4.3). This is because the crash would usually be caused by a variable with an invalid value
assigned to it by a previous line of code. This value could in turn be derived from another variable with an
incorrect value assigned by a previous line of code, and so on.

For instance, in the program below, a run-time error occurs on line 11 because the contents of values are
invalid for summing. However, this is actually caused by a logic error introduced on line 7, where strs are
being inserted into values instead of ints.

On the other hand, a syntax error prevents code from being run at all. Hence, a syntax error is generally not
considered to be a run-time error.

1
2
3
4
5
6
7 Run-time error on line 11 is actually
8 caused by missing type conversion
on line 7.
9
10
11
12
13
14
15
16
17

Enter integer, blank to end: 2

Enter integer, blank to end: 0

Enter integer, blank to end: 2

Enter integer, blank to end: 4

Enter integer, blank to end

242
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [1], in <cell line: 10>()
9 # Process
10 if len(values) > 0:
---> 11 sum_values = sum(values)
12 average = sum_values / len(values)
13 else:

TypeError: unsupported operand type(s) for +: 'int' and 'str'

Figure 6.8 Run-time error occurs on line 11 but is caused by


logic error on line 7

QUICK
C K 6
3 . 32
CHE
1. The following program that prints the multiplication table for an integer between 1 to 10 (inclusive) contains
several syntax errors.

Identify and correct the errors.

1
2
3
4
5
6
7
8
9
10
11
12

2. State whether each of following statements are true or false.


a) Dividing a number by zero is an example of a runtime error.
b) A missing colon after the condition of a while loop is an example of a logic error.
c) A logic error will always cause the program to produce an error message.
d) A runtime error can still occur even if there are no syntax or logic errors in the program.

243
6.3 Designing Test Cases

LEARNING OUTCOMES
2.4.9 Design appropriate test cases to cover normal, error and boundary conditions and specify
which type(s) of conditions is/are being tested for each test case.

6.3.1 Why Testing is Needed

To detect run-time errors and logic


errors, it is important to perform ER MS
testing. This is done by running the KEY T
program through one or more test
cases to evaluate whether it solves Test case
a problem correctly. A test case A set of inputs and the corresponding set of expected outputs
usually consists of a set of inputs and for a particular program which is used to verify whether the
the corresponding set of expected program works as intended
outputs. By feeding these inputs into
a program and comparing its actual Testing
output with the expected output, we The process by which defects in a program can be detected
can evaluate whether the program is
working as intended.

Designing good test cases is important because it is usually impossible to test every possible input
combination. To ensure that we do not miss out on any situations where errors are likely to occur, test cases
should cover three types of conditions:

Normal conditions Boundary conditions Error conditions

To study the three types of conditions, let us consider the problem of converting a phrase into an
acronym. An acronym is an abbreviation formed from the first letter of each word. Acronyms must usually
be pronounceable, but for our problem, we simply want to combine the first letter of each word into a
capitalised abbreviation regardless of whether the result is pronounceable.

The input and output requirements for this problem are specified in Table 6.1.

Input Output

• input_text: phrase to • Capitalised acronym of


be used for generating the given phrase
acronym, should contain
only letters and spaces

Table 6.1 Input and output requirements for the problem of


converting a phrase into an acronym

244
6.3.2 Normal Conditions

First, let us design test cases to make sure that the program to be tested works as intended under normal
conditions – situations where the input data is what we expect to be provided with during normal use of
the program. In general, under normal conditions, at least one test case should be designed for each type
of expected input. In this case, only one type of input is expected (a str), so we will design a test case
accordingly:

→ Input Expected Output →

• input_text: "Computer Science" CS

Table 6.2 Test Case 1 for acronym generation problem

However, we also want to be sure that the test cases for normal conditions accurately represent the variety
of inputs that we are expecting. To test this, we shall design an additional test case so that words that start
with both upper-case and lower-case letters are represented.

→ Input Expected Output →

• input_text: "For your info" FYI

Table 6.3 Test Case 2 for acronym generation problem

Test cases for normal conditions are the most straightforward and will usually be the first test cases to be
written.

6.3.3 Boundary Conditions

Next, let us design test cases to make sure that the program works as intended under boundary conditions
– situations where the input data is at the limit of what the program is designed for, or where special
handling of the input data is required.

• The limits of a program are usually in terms of quantity (for int and oat inputs) and/ or length (for
list and str inputs).
• On the other hand, special handling is usually needed when the problem definition specifies that
certain values are to be treated as exceptions (such as using −1 to represent missing data in a
survey).

Note that boundary conditions may overlap with normal conditions and error conditions as input data
under boundary conditions is still classified as either valid (i.e., normal) or invalid (i.e., erroneous). For
situations with minimum or maximum limits for an input, test cases should be designed for both sides
of the boundary to ensure that the limits are enforced correctly. For instance, if an integer input must be
greater than 0, test cases should be written for both the valid boundary value of 1 and the invalid boundary
value of 0.

245
Boundary conditions

... -2 -1 0 1 2 3 4

Error conditions Normal conditions

Figure 6.9 Normal, error and boundary conditions for integer input
that must be greater than 0

For the acronym generation problem,


there is no defined maximum length for → Input Expected Output →
the input phrase, so it is not possible
to design a test case that would be at • input_text: "" (blank output)
the upper limit of what the program
can handle. However, we do want the
program to work as intended even with Table 6.4 Test Case 3 for acronym generation problem
the minimum length of input possible,
that is, an empty str. In this case, we
expect the correct output to also be an → Input Expected Output →
empty str, as shown in Test Case 3.
• input_text: CS
Under normal conditions, we would
expect the input phrase to be a sequence " Computer Science "
of words with a single space in between.
However, the program must also be able Table 6.5 Test Case 4 for acronym generation problem
to run in the special case where additional
spaces are provided before and after
words. Let us design an additional test
case to cover such situations.

For Test Case 4, the expected output for " Computer Science " is exactly the same as the expected
output for "Computer Science" (with no additional spaces).

Determining the boundary conditions for a problem and the expected output for each case can be tedious
as well as difficult. However, this process is necessary to ensure that the program behaves as intended
regardless of the input supplied.

6.3.4 Error Conditions

Finally, let us design test cases to make sure that the program behaves as expected under error conditions
– situations where the input data would normally be rejected by the program via input validation (see
Chapter 5).

By definition, error conditions and normal conditions are mutually exclusive.

For the acronym generation problem, it is clearly stated that the input phrase should contain only letters
and spaces. Based on this definition, any input that contains punctuation symbols or digits should generate
an error. The following two test cases cover these situations:

246
→ Input Expected Output →

• input_text: Data validation failed!


"Lead, Care, Inspire" Input text should only
contain letters and spaces

Table 6.6 Test Case 5 for acronym generation problem

→ Input Expected Output →

• input_text: Data validation failed!


"Computing 2024" Input text should only
contain letters and spaces

Table 6.7 Test Case 6 for acronym generation problem

Test cases for error conditions are necessary because programs that fail to reject invalid input may end
up performing unintended or even harmful actions. For instance, a large number of security flaws in
programs today are caused by the improper handling of error conditions. A typical example in some
programming languages would be forgetting to check whether the input data supplied by a user can
fit into the memory space allocated to store the data. A program that tries to store this invalid data
will end up overwriting subsequent areas of memory and allow potential attackers to insert their own
instructions into the program.

QUICK 3
C K 6
3 .
CHE
1. A student wants to solve the following problem:

Input Output
• n: number of “*” characters needed for • a text-based square made out of “*”
each side of the square, should be an characters with n “*” characters on each
integer between 1 and 20 inclusive side, square should be filled in with “*”
characters with no other characters inside

Table 6.8 Input and output requirements for the problem of drawing a square

The following are two test cases under normal conditions for this problem. (Note that although the expected
outputs may appear rectangular, they can be considered as squares as they have the same number of characters
on each side.)

→ Input Expected Output →


• n: 5 *****
*****
*****
*****
*****

Table 6.9 Test Case 1 for square drawing problem

247
QUICK 3
C K 6
3 .
CHE
→ Input Expected Output →
• n: 10 **********
**********
**********
**********
**********
**********
**********
**********
**********
**********

Table 6.10 Test Case 2 for square drawing problem

a) Design another test case that covers normal conditions for this problem.
b) Design two test cases that cover the boundary conditions for this problem. For each test case, explain why it
covers a boundary condition.
c) Design two test cases that cover the error conditions for this problem. For each test case, explain why it
covers an error condition. (You may choose your own expected error message.)

Questions 2 and 3 involve programs meant to solve the following diamond drawing problem:

Input Output
• n: height of diamond in number • a text-based symmetrical diamond
of lines (and also the number of “*” made out of “*” and “ ” (space) characters
characters needed for the widest part of with a height of n lines and width of n
the diamond), should be an odd integer “*” characters for the widest part of the
between 1 and 19 inclusive diamond, diamond should be filled in with
“*” characters with no other characters
inside

Table 6.11 Input and output requirements for the problem of drawing a diamond

The following is a test case under normal conditions for this problem:

→ Input Expected Output →


• n: 5 *
***
*****
***
*

Table 6.12 Test case for diamond drawing problem

2. For each of the following inputs:

i. State whether the input falls under normal conditions, boundary conditions and/or error conditions
for this problem.
ii. Design a corresponding test case by specifying the expected output. (If needed, you may choose your own
expected error message.)

a) Input: n = 1
b) Input: n = 6
c) Input: n = 7

248
QUICK 3
C K 6
3 .
CHE
3. The program below for the diamond drawing problem has three syntax errors.
a) Identify where the syntax errors are.
b) Rewrite the program to fix these syntax errors.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Figure 6.10 Program for diamond drawing problem with syntax errors

249
6.4 Common Debugging Techniques

LEARNING OUTCOMES
2.4.1 Produce a trace table by performing a manual dry run and inspecting the value of variables
at each step of a program.

2.4.2 Inspect the value of variables at selected steps of a program by inserting print statements.

2.4.3 Locate logic errors by backtracking from a point where unexpected behaviour is observed.

2.4.4 Test programs incrementally as small additions and changes are made during development.

2.4.5 Test small parts of a program by commenting out other parts of the program that are not
needed for testing.

To demonstrate how debugging is performed, let us work on a draft program to solve the acronym
generation problem. Since words usually have a space before them, we can try to loop through input
text and extract all the characters that come after a space:

1
2
3
4
5
6
7
8
9
10
11

Figure 6.11 Draft program to solve the acronym generation problem

However, when we try to run the program on Test Case 1, we find that its output is different from what
is expected.

→ Input Expected Output →

input_text: "Computer Science" CS

Running acronym_draft.ipynb on Test Case 1 → Result

Enter the input text: Computer Science Failed


S

Table 6.13 Running acronym_draft.ipynb on Test Case 1

This is a sign that there is a bug. Let us look at some common debugging techniques to fix it.

250
6.4.1 Using Print Statements

One simple yet effective debugging technique is to keep track of how variables change as the program runs
using the print() function. By adding a print statement that outputs the value of important variables
inside a loop, we can get valuable information on how the variables change over time. For instance, the
program below has been modified with the addition of line 9, which prints out the values of index and
acronym each time the loop repeats.

1
2
3
4
5
6
7
8
9
10
11
12

Figure 6.12 Draft program with addition of print statement on line 9 (highlighted)

Running this new program on Test Case 1 reveals how these two variables change over time as characters
are extracted from input_text to form the acronym.

→ Input Expected Output →

input_text: "Computer Science" CS

Running acronym_draft_2.ipynb on Test Case 1 → Result

Enter the input text: Computer Science Failed


DEBUG: index=0, acronym=
DEBUG: index=1, acronym=
DEBUG: index=2, acronym=
DEBUG: index=3, acronym=
DEBUG: index=4, acronym=
DEBUG: index=5, acronym=
DEBUG: index=6, acronym=
DEBUG: index=7, acronym=
DEBUG: index=8, acronym=
DEBUG: index=9, acronym=S
DEBUG: index=10, acronym=S
DEBUG: index=11, acronym=S
DEBUG: index=12, acronym=S
DEBUG: index=13, acronym=S
DEBUG: index=14, acronym=S
DEBUG: index=15, acronym=S
S

Table 6.14 Running acronym_draft_2.ipynb on Test Case 1

From the debugging output, we can see that the first letter of “Science” is correctly extracted into the
acronym when index reaches 9. However, the first letter of “Computer” is not.

251
6.4.2 Using a Trace Table

An alternative to using print statements is to perform a


ERMS dry run where the steps of a program or algorithm are
KEY T followed one by one manually (with input data from a
test case if needed). The result of a dry run is a trace
Dry run table which records the change in variables and the
The process of running through a set of output of the algorithm during the run.
steps manually
A trace table always has one column for every variable
Trace table in the program and an additional OUTPUT column on
A tabulation of changes in the variables the right. Each time an output is produced, or a value
and output of an algorithm when is assigned to a variable, the output or new value is
performing a dry run recorded under the corresponding column. Entries are
usually recorded in the last non-empty row, except
at the start (when the table is blank) or when the last
non-empty row already has an entry in that column. In
both cases, use the next empty row instead.

For instance, Table 6.15 shows the completed trace table from performing a dry run on the program in
Figure 6.11 with the input from Test Case 1:

→ Input_text acronym index Output →

"Computer Science" "" 0


1
2
3
4
5
6
7
8
"S" 9
10
11
12
13
14
15 S

Table 6.15 Completed trace table from the dry run

252
We see that a trace table can provide the same information as using print statements but the process is
manual. On the other hand, trace tables do not require you to modify the program and can be used with
algorithms outside of programming.

U
DID YO
N O W ? An alternative method of filling in a truth table is to add a new row every time
K output is produced, or a value is assigned to a variable, instead of only when
a new row is needed. However, this method can require much more space. In
general, alternative methods are acceptable as long as the trace table allows
you to analyse the program chronologically when read from top to bottom.

6.4.3 Backtracking

Backtracking is the technique of working


backwards from where unintended
ER MS
behaviour is observed to find out the logic
error that caused it.
KEY T
Backtracking
Usually, we would backtrack from a point A debugging technique of working backwards from
where there is a bug. However, as an initial a point where unexpected behaviour is observed so
demonstration, let us backtrack from as to determine its cause
when index is 9 and the debug output
from line 9 shows that the first letter of
“Science” is correctly extracted.

Working backwards to line 7, we see that when


index is 9, the program correctly subtracts 1
from index and tests if the previous character at
input_text[8] is a space. Since input_text[8]
is indeed a space, line 8 is run and input_text[9]
or “S” is appended to acronym. This is the intended
behaviour.

Now, let us backtrack from where there is a bug.


This occurs at the very beginning when index is 0
and the program fails to extract the first letter of
“Computer”.

Working backwards to line 7, we see that when


index is 0, the program is actually testing
Figure 6.13 Backtracking requires looking back carefully at whether input_text[-1] (i.e., the last letter of
the steps that resulted in an incorrect output input_text) is a space before deciding not to
include input_text[0] in the acronym. This is
not the intended behaviour and is caused by the
fact that there is no “previous character” for the
first character of a string.

This demonstrates how backtracking can be used


to detect and understand “hidden” logic errors
that cause unintended behaviour.

253
6.4.4 Testing Incrementally

When writing or editing programs, we may be tempted to write or change a large amount of code at once.
However, if the resulting program fails to work, it becomes harder to narrow down where the error may be
located. Implementing too many features at once also makes it more likely that some features do not get
thoroughly tested.

Instead, a good practice is to test programs incrementally as small additions and changes are made during
development. This allows us to test new features or changes more thoroughly and verify that the small
amount of code added or modified works as intended before moving on. If a bug is detected, debugging
will also be easier given the smaller amount of code that likely caused the error.

For the acronym generation program, we have discovered the cause of why our program fails Test Case
1 and may have some ideas on how to fix it. Let us demonstrate how we might implement these fixes by
testing incrementally while making small changes.

We know Test Case 1 fails because there is no “previous character” for the first character of a string and
subtracting 1 when index is 0 will result in a negative index. Let us try to fix this bug by checking for this
special case on line 7.

1
2
3
4
5
6
7
8
9
10
11
12
13

Figure 6.14 Draft program with attempt at fixing first character bug

254
After making this small change, let us incrementally run Test Case 1 again:

→ Input Expected Output →

input_text: "Computer Science" CS

Running acronym_draft_3.ipynb on Test Case 1 → Result

Enter the input text: Computer Science Failed


DEBUG: index=0, acronym=
DEBUG: index=1, acronym=
DEBUG: index=2, acronym=
DEBUG: index=3, acronym=
DEBUG: index=4, acronym=
DEBUG: index=5, acronym=
DEBUG: index=6, acronym=
DEBUG: index=7, acronym=
DEBUG: index=8, acronym=
DEBUG: index=9, acronym=S
DEBUG: index=10, acronym=S
DEBUG: index=11, acronym=S
DEBUG: index=12, acronym=S
DEBUG: index=13, acronym=S
DEBUG: index=14, acronym=S
DEBUG: index=15, acronym=S
S

Table 6.16 Running acronym_draft_3.ipynb on Test Case 1

It turns out our change does not affect the program’s output at all!

Backtracking from the output at line 10 when index is 0, we see that on line 7 the if condition evaluates
to False as intended, preventing us from incorrectly using a negative index on line 8. However, this also
means we skip line 9 and the first letter of “Computer” is not appended to acronym.

Let us make another small change to address this issue. This time, we will make a copy of line 9 that always
runs when index is 0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Figure 6.15 Draft program with another attempt at fixing first character bug

255
Running this amended program on Test Case 1 now produces the expected output (if we ignore the lines of
debugging output that start with “DEBUG”).

→ Input Expected Output →

input_text: "Computer Science" CS

Running acronym_draft_4.ipynb on Test Case 1 → Result

Enter the input text: Computer Science Passed


DEBUG: index=0, acronym=C
DEBUG: index=1, acronym=C
DEBUG: index=2, acronym=C
DEBUG: index=3, acronym=C
DEBUG: index=4, acronym=C
DEBUG: index=5, acronym=C
DEBUG: index=6, acronym=C
DEBUG: index=7, acronym=C
DEBUG: index=8, acronym=C
DEBUG: index=9, acronym=C
DEBUG: index=10, acronym=CS
DEBUG: index=11, acronym=CS
DEBUG: index=12, acronym=CS
DEBUG: index=13, acronym=CS
DEBUG: index=14, acronym=CS
DEBUG: index=15, acronym=CS
CS

Table 6.17 Running acronym_draft_4.ipynb on Test Case 1

We can refine the program further by removing the print statement used for debugging, removing repeated
lines of code and consolidating the checks for each character into a function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Figure 6.16 Draft program with checks consolidated into a function

256
Once again, we test incrementally and find that the program still passes Test Case 1.

→ Input Expected Output →

input_text: "Computer Science" CS

Running acronym_draft_5.ipynb on Test Case 1 → Result

Enter the input text: Computer Science Passed


CS

Table 6.18 Running acronym_draft_5.ipynb on Test Case 1

However, when we run the program on Test Case 2, we receive an unexpected output.

→ Input Expected Output →

input_text: "For your info" FYI

Running acronym_draft_5.ipynb on Test Case 2 → Result

Enter the input text: For your info Failed


Fyi

Table 6.19 Running acronym_draft_5.ipynb on Test Case 2

Fortunately, the cause of this bug is quite clear: We did not convert the output to upper case. We make
another small change by replacing the print statement used for debugging and adding a line to convert the
output to upper case when the program has exited the loop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Figure 6.17 Draft program with letter case bug fixed by adding the highlighted code on line 14

257
The program still passes Test Case 1 and running the amended program on Test Case 2 now generates the
expected output.

→ Input Expected Output →

input_text: "For your info" FYI

Running acronym_draft_6.ipynb on Test Case 2 → Result

Enter the input text: For your info Passed


FYI

Table 6.20 Running acronym_draft_6.ipynb on Test Case 3

This process demonstrates how running and testing programs incrementally as small additions and
changes are made can make it easier to locate and fix errors, compared to making a large number of
additions or changes before testing.

6.4.5 Testing Small Parts of a Program

Let us continue running our program on the test cases we designed. The program behaves as intended for
Test Case 3 under the boundary conditions of having an empty str as input.

→ Input Expected Output →

input_text: "" (blank output)

Running acronym_draft_6.ipynb on Test Case 3 → Result

Enter the input text: Passed

Table 6.21 Running acronym_draft_6.ipynb on Test Case 3

However, running the program on Test Case 4 produces something unexpected.

→ Input Expected Output →

input_text:" Computer Science " CS

Running acronym_draft_6.ipynb on Test Case 4 → Result

Enter the input text: Computer Science Failed


C S

Table 6.22 Running acronym_draft_6.ipynb on Test Case 4

258
From the output, it looks like the acronym has some extra spaces that should not be there. To investigate
the cause of this bug, we shall break down the program into smaller parts and test the parts one at a time.
This allows us to narrow down the possible locations of any bugs and reduce the amount of code that we
need to study at any one time.

To test only one part of a program at a time, we can temporarily disable lines of code by inserting a
hash symbol (#) at the start of these lines. This process is called commenting out code since it works by
temporarily turning the code into comments.

We start by disabling code that is already working and not likely to be where the error is located. From the
output, we know the letters “C” and “S” that are part of the expected output were extracted correctly, so
this implies that the following processes are all working:

1
Getting input from the
2
keyboard
3
4
5
Iterating through the
6
input characters
7
8
9
10
11
12
Adding characters to
13
the acronym
14
15
16
17
Displaying the acronym
on the screen
18
19
20
21
22

Figure 6.18 Testing a function with different input arguments to confirm location of bug

U
DID YO
Hence, we can deduce that the error is located in should_include()
as part of the code that tests if the current character should be part

KNOW?
of the acronym (line 12). We can also eliminate the possibility that
the error is located at the code converting the acronym to upper case
(lines 15) as it is not relevant in this case.

To test functions like should_include(), we can run mini test cases. A quick way to comment
We comment out all other code except for the function definition. At and uncomment code in
the end of the program, we then call should_include() repeatedly Jupyter Desktop is to select
with different combinations of input arguments to determine if the it and use the keyboard
function is working as expected. This is shown in Figure 6.18. shortcut Ctrl-/.

259
In this case, the expected outputs for the three calls to should_include() are False, False and
True. This is because the first three characters of s are " C". When index is either 0 or 1, we expect
should_include() to return False since both s[0] and s[1] are spaces and spaces should not appear in
acronyms. On the other hand, when index is 2 we expect should_include() to return True since s[2] is
precisely the first letter of the word “Computer”.

However, when we run the program, the actual outputs of True, True and True do not match the expected
outputs:

→ Input Expected Output →

Mini Test Case 1 False


• s: " Computer Science " False
• index: 0 True
Mini Test Case 2
• s: " Computer Science "
• index: 1
Mini Test Case 3
• s: " Computer Science "
• index: 2

Running acronym_draft_7.ipynb on Test Case 4 → Result


True Failed
True
True

Table 6.23 Running acronym_draft_7.ipynb on mini test cases

To fix this bug, we can add an additional condition to should_include() – In this instance, the character
being tested must not be a space.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Figure 6.19 Draft program with unwanted spaces bug fixed by adding the highlighted code

260
This modified program now passes Test Case 4.

→ Input Expected Output →

input_text:" Computer Science " CS

Running acronym_draft_8.ipynb on Test Case 4 → Result

Enter the input text: Computer Science Passed


CS

Table 6.24 Running acronym_draft_8.ipynb on Test Case 4

However, our program still does not pass Test Case 5 and Test Case 6 because it does not perform any data
validation. To fix this, a format check is added to the program to ensure that the input phrase contains
letters and spaces only, as shown in Figure 6.20.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Figure 6.20 Format check (highlighted) added to the program

261
This final version of the program passes Test Case 5 and Test Case 6.

→ Input Expected Output →

input_text: "Lead, Care, Inspire" Data validation failed!


Input text should only contain
letters and spaces

Running acronym_final.ipynb on Test Case 5 → Result

Enter the input text: Lead, Care, Inspire Passed


Data validation failed!
Input text should only contain letters and spaces

Table 6.25 Running acronym_final.ipynb on Test Case 5

→ Input Expected Output →

input_text: "Computing 2024" Data validation failed!


Input text should only contain
letters and spaces

Running acronym_final.ipynb on Test Case 6 → Result

Enter the input text: Computing 2024 Passed


Data validation failed!
Input text should only contain letters and spaces

Table 6.26 Running acronym_final.ipynb on Test Case 6

Since the program passes all the test cases we have designed, we can be confident that it works as intended
in solving the acronym generation problem.

262
QUICK 4
C K 6.
CHE
1. Produce a trace table for the following program given an input of 5:

1
2
3
4
5
6
7
8
9
10
11

Figure 6.21 Program for trace table

2. A new program has been written to solve the acronym generation problem. State whether it passes or fails each
of the six test cases we have designed for the acronym generation problem.

1
2
3
4
5
6
7
8
9
10
11
12
13

Figure 6.22 New program for the acronym generation problem

3. The following is a description of the diamond drawing problem:

Input Output
• n: height of diamond in number of • a text-based symmetrical diamond
lines (and also the number of “*” made out of “*” and “ ” (space)
characters needed for the widest part characters with a height of n lines and
of the diamond), should be an odd width of n “*” characters for the widest
integer between 1 and 19 inclusive part of the diamond, diamond should
be filled in with “*” characters only

The following is a test case under normal conditions for this problem:

→ Input Expected Output →


• n: 5 *
***
*****
***
*

263
QUICK 4
C K 6.
CHE
The following program for the diamond drawing problem crashes with a run-time error on line 25 when it runs
on the given test case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Figure 6.23 Program for diamond drawing problem that crashes

→ Input Expected Output →

n: 5 *
***
*****
***
*

Running diamond_crash.ipynb on Test Case → Result

Enter n: 5 Failed
*
***
***
*

-----------------------------------------------------
----------------------
IndexError Traceback
(most recent call last)
Input In [1], in <cell line: 24>()
23 print(stars[index])
24 for index in range(n // 2 + 1):
---> 25 print(stars[-index - 1])

IndexError: list index out of range

Table 6.29 Running diamond_crash.ipynb on Test Case

264
QUICK
EC K 6.4
CH
The program uses a list named stars to store the lines of text that make up the diamond before printing them
in a specific order. stars is initialised in lines 16 to 20.

a) By commenting out other parts of the program or otherwise, modify the program to print out the contents
of stars after it is initialised for n = 5. The program should not produce any other output. (Take note of the
number of items in stars.)

b) Undo any changes from part (a). By using print statements and backtracking from the error on line 25 or
otherwise, explain why the program crashes for n = 5.

c) Fix the program by changing only one line and state whether line 25 should be the line of code to change.

W
REVIE
ES TI ON
QU
1. A program is needed to solve the following word reversal problem:

Input Output
• input_text: phrase to reverse, • Given phrase with the order of
should contain only letters and the letters of each word reversed
spaces but with the words themselves
still in order, separated by one
space between each word

Table 6.30 Input and output requirements for the word reversal problem

The draft program in Figure 6.24 is written for this purpose.

# Program: word_reversal_draft.ipynb
1 # Input
2 input_text = input("Enter input text: ")
3
4 # Process
5 word_list = input_text.split()
6
7 for i in range(len(word_list)):
8 current_word = ""
9 for letter_index in range(len(word_list[i])):
10 current_word += word_list[i][-letter_index]
11 word_list[i] = current_word
12
13 result = ""
14 for word in word_list:
15 result += word + " "
16 result = result[:-1]
17
18 # Output
19 print(result)

Figure 6.24 Draft program for the word reversal problem

265
W
REVIE
E ST IO N
QU
a) The main algorithm for the program consists of three parts. Read through the code and state what each of
the following parts of the program are intended to do:
i. Line 5
ii. Lines 7 to 11
iii. Lines 13 to 16

b) When this program is run on the following test case, the actual output does not match the expected
output:

→ Input Expected Output →


• input_text: "For your info" roF ruoy ofni
Running word_reversal_draft.ipynb on a test case → Result
Enter the input text: For your info Failed
Fro yruo iofn

Table 6.31 Running word_reversal_draft.ipynb on a test case

i. State the kind of condition (normal, boundary or error) covered by this test case.

ii. State whether the program has a syntax error or a logic error and explain why.

iii. Find out where the error is located by disabling lines of code and testing the program in chunks or parts.
State the line on which the error is located and explain why the line is incorrect.

iv. Rewrite the program to fix the error so that it passes the test case.

2. The program in Figure 6.25 accepts a string of alphabetical letters only and outputs the string with its
upper-case letters converted to lower-case and vice versa. For example, if the input were “comPUting”, then
the output would be “COMpuTING”.

# opposite_case_unfinished.ipynb
1 # Returns True if s has alphabetical letters only
2 def is_letters_only(s):
3 # TODO: See part (b)
4
5 # Converts single letter to opposite case
6 def convert_letter(l):
7 if l.islower():
8 return l.upper()
9 return l.lower()
10
11 # Converts string of letters to opposite case
12 def convert_string(s):
13 result = ''
14 for c in s:
15 result = convert_letter(c)
16 return result
17
18 while True:
19 s = input('Enter string: ')
20 if is_letters_only(s):
21 break
22 print('String must have alphabetical letters only')
23
24 print(convert_string(s))

Figure 6.25 Draft program for the opposite case problem

a) The is_letters_only() function is supposed to return True if its parameter is made up of alphabetical
letters only. Write down the expected return value for each of the following test cases:

i. is_letters_only('hello')
ii. is_letters_only('Hello World')
iii. is_letters_only('HelloWorld')
iv. is_letters_only('Hello, World!')

266
W
REVIE
E ST IO N
QU
b) Using an empty string as input is a boundary condition for the is_letters_only() function. Boundary
conditions can be difficult to handle as there may be multiple options for expected behavior.

i. Suggest one possible reason why the return value for is_letters_only('') should be True.

ii. Suggest one possible reason why the return value for is_letters_only('') should be False.

iii. Suppose an empty string should be treated as valid input for this program. Choose an appropriate return
value for is_letters_only('') and fill in the function body for is_letters_only() accordingly. (To test
your code, temporarily comment out the main program and run test cases.)

c) Even with is_letters_only() correctly filled in, there is a bug in the program that causes its output to be
different from what is expected. For example, if the input were “comPUting”, the program will output “G”
instead of the expected “COMpuTING”.

i. Explain why a bug in convert_letter() can affect convert_string().

ii. Find the bug in convert_string() and suggest how it can be fixed.

ANSWER
Pg. 243-Quick Check 6.2
1.
# Program: multiplication_table_fixed.ipynb
1 # Input
2 while True:
3 number = input('Enter integer (1-10): ')
4 if number.isdigit():
5 number = int(number)
6 if number >= 1 and number <= 10:
7 break
8 print('Data validation failed!')
9
10 # Process and Output
11 for i in range(1, 11):
12 print(f'{number} * {i} = {number * i}')

The syntax errors are:


• Line 3: Ending quotation mark should be ' instead of " to match the opening quotation mark
• Line 4: Missing colon after if condition
• Line 6: Correct “greater than or equal to” operator is >= instead of =>
• Line 11: Slicing operator syntax is not valid in function call; correct call should be range(1, 11) instead of
range(1:11)
• Line 12: Function call should end with ) instead of }

2. a) True
b) False. It is a syntax error.
c) False. A program with a logic error may not display an error message.
d) True. Run-time errors can occur due to conditions outside of the program’s control (e.g., running out of
memory).

267
ANSWER
Pg. 247-Quick Check 6.3
1. a) Test case that covers normal conditions (accept any possible answer):

→ Input Expected Output →


• n: 7 *******
*******
*******
*******
*******
*******
*******

b) Test cases for the boundary condition when n is at its lower limit:

→ Input Expected Output →


• n: 0 Data validation failed!
n should be an integer
between 1 and 20 inclusive

→ Input Expected Output →


• n: 1 *

These are boundary conditions as the inputs of 0 and 1 cover both sides of the boundary where n is at its
lower limit.

Test cases for the boundary condition when n is at its upper limit:

→ Input Expected Output →


• n: 20 ********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************
********************

→ Input Expected Output →


• n: 21 Data validation failed!
n should be an integer
between 1 and 20 inclusive

These are boundary conditions as the inputs of 20 and 21 cover both sides of the boundary where n is at its
upper limit.

c) As n is defined as an integer between 1 and 20 inclusive, an input of -1 is not within the valid range of values
of n and should be considered as an error condition:

268
ANSWER

→ Input Expected Output →


• n: -1 Data validation failed!
n should be an integer
between 1 and 20 inclusive

Similarly, any non-integer input should also be considered as an error condition:

→ Input Expected Output →


• n: "a" Data validation failed!
n should be an integer
between 1 and 20 inclusive

2. a)
i. Boundary / Normal condition
ii.
→ Input Expected Output →
• n: 1 *

b)
i. Error condition
ii.
→ Input Expected Output →
• n: 6 Data validation failed!
n should be an odd integer
between 1 and 19 inclusive

c)
i. Normal condition
ii.
→ Input Expected Output →
• n: 7 *
***
*****
*******
*****
***
*

3. a) The three errors are located at:


1. Line 2: while is spelled incorrectly.
2. Line 6: The if condition needs to end with a colon (:).
3. Line 19: There is an extra right bracket at the end of the line.
b) The corrected program is as follows:

# Program: diamond_with_syntax_errors_fixed.ipynb
1 # Input and input validation
2 while True:
3 input_text = input("Enter n: ")
4 if input_text.isdigit():
5 n = int(input_text)
6 if n >= 1 and n <= 19 and n % 2 == 1:
7 break
8 else:
9 print("Data validation failed!")
10 print("n should be odd and between 1 and 19 inclusive")
11 else:
12 print("Data validation failed!')
13 print("n should be a positive integer")
14
15 # Process and output
16 for index in range(n // 2):
17 print(' ' * (n // 2 - index) + '*' * (2 * index + 1))
18 for index in range(n // 2 + 1):
19 print(' ' * index + '*' * (n - index * 2))

269
ANSWER

Pg. 263-Quick Check 6.4


1.
n num1 num2 i temp OUTPUT
5 1 1 0 2 1
1 2 1 3 1
2 3 2 5 2
3 5 3 8 3
5 8 4 13 5
8 13

2. acronym_alternative.ipynb passes all test cases except Test Cases 5 and 6 as


the program does not perform any input validation.

3. a) The modified program may be:

# Program: diamond_debugging.ipynb
1 # # Input and input validation
2 # while True:
3 # n_str = input("Enter n: ")
4 # if not n_str.isdigit():
5 # print("Data validation failed!")
6 # print("n should be a positive integer")
7 # continue
8 # n = int(n_str)
9 # if n < 1 or n > 19 or n % 2 == 0:
10 # print("Data validation failed!")
11 # print("n should be odd and between 1 and 19 inclusive")
12 # continue
13 # break
14
15 # Process and output
16 n = 5
17 stars = []
18 index = 1
19 while index < n:
20 stars += [" " * ((n - index) // 2) + "*" * index]
21 index += 2
22 print(stars)
23
24 # for index in range(n // 2):
25 # print(stars[index])
26 # for index in range(n // 2 + 1):
27 # print(stars[-index - 1]

[' *', ' ***']

b) We can add a print statement to track the value of index up to when the program crashes:

# Program: diamond_debugging_2.ipynb
1 Input and input validation
2 while True:
3 n_str = input("Enter n: ")
4 if not n_str.isdigit():
5 print("Data validation failed!")
6 print("n should be a positive integer")
7 continue
8 n = int(n_str)
9 if n < 1 or n > 19 or n % 2 == 0:
10 print("Data validation failed!")
11 print("n should be odd and between 1 and 19 inclusive")
12 continue
13 break
14

270
ANSWER

# Program: diamond_debugging_2.ipynb
15 # Process and output
16 stars = []
17 index = 1
18 while index < n:
19 stars += [" " * ((n - index) // 2) + "*" * index]
20 index += 2
21
22 for index in range(n // 2):
23 print(stars[index])
24 for index in range(n // 2 + 1):
25 print(f"DEBUG: index={index}")
26 print(stars[-index - 1])

Enter n: 5
*
***
DEBUG: index=0
***
DEBUG: index=1
*
DEBUG: index=2
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Input In [1], in <cell line: 24>()
24 for index in range(n // 2 + 1):
25 print(f"DEBUG: index={index}")
---> 26 print(stars[-index - 1])

IndexError: list index out of range

The debug output shows that index is 2 when the program crashes. This indicates that -index - 1 on the last
line evaluates to -3. Using our results from part (a), we know that stars has only 2 str items, so -3 is indeed out
of range. Working backwards, we see that the program crashes because stars is missing the widest line of the
diamond (i.e., "*****") due to a logic error.

c) The most straightforward way to fix this error is to change the condition on line 18 from “index < n” to
“index <= n”.

The corrected program is as follows:

# Program: diamond_fixed.ipynb
1 Input and input validation
2 while True:
3 n_str = input("Enter n: ")
4 if not n_str.isdigit():
5 print("Data validation failed!")
6 print("n should be a positive integer")
7 continue
8 n = int(n_str)
9 if n < 1 or n > 19 or n % 2 == 0:
10 print("Data validation failed!")
11 print("n should be odd and between 1 and 19 inclusive")
12 continue
13 break
14
15 # Process and output
16 stars = []
17 index = 1
18 while index <= n:
19 stars += [" " * ((n - index) // 2) + "*" * index]
20 index += 2
21
22 for index in range(n // 2):
23 print(stars[index])
24 for index in range(n // 2 + 1):
25 print(stars[-index - 1])

271
ANSWER

Pg. 265-Review Questions


1.
a) i. Line 5 splits input_text into a list of words (word_list) that are separated by spaces.
ii. Lines 7 to 11 reverse the order of the letters in each word in word_list.
iii. Lines 13 to 16 join the contents of word_list together using spaces.
b) i. Normal condition
ii. A logic error has occurred. While the program has not crashed, its actual output is different from the
expected output.
iii. The error is located on line 10 and is caused by using the indexing operator to count from the end of a
string incorrectly. (The last character of a string is at index −1, not index −0.)
iv. The corrected program is as follows:

# Program:word_reversal.ipynb
1 # Input
2 input_text = input("Enter input text: ")
3
4 # Process
5 word_list = input_text.split()
6
7 for i in range(len(word_list)):
8 current_word = ""
9 for letter_index in range(len(word_list[i])):
10 current_word += word_list[index][-letter_index - 1]
11 word_list[i] = current_word
12
13 result = ""
14 for word in word_list:
15 result += word + " "
16 result = result[:-1]
17
18 # Output
19 print(result)

2.
a) i. True
ii. False
iii. True
iv. False

b) i. The return value should be True since an empty string, having no characters, trivially satisfies the
requirement of not containing any non-letter characters.
ii. The return value should be False since an empty string, having no characters, is not a sensible input for
the task of checking whether every character is a letter.
iii. Since an empty string is considered valid input, we choose to have is_letters_only('') return True so
an empty string will pass data validation. A possible answer is as follows:

# Returns True if s has alphabetical letters only


def is_letters_only(s):
for c in s:
if not c.isalpha():
return False
return True

c) i. This is because convert_string() calls and uses the return value of convert_letter() in its function
body to determine its own return value. An incorrect return value from convert_letter() is likely to
result in an incorrect return value for convert_string().
ii. The bug is in convert_string(). A possible answer is as follows:

272
ANSWER

# Program: opposite_case_finished.ipynb
1 # Returns True if s has alphabetical letters only
2 def is_letters_only(s):
3 for c in s:
4 if not c.isalpha():
5 return False
6 return True
7
8 # Converts single letter to opposite case
9 def convert_letter(l):
10 if l.islower():
11 return l.upper()
12 return l.lower()
13
14 # Converts string of letters to opposite case
15 def convert_string(s):
16 result = ''
17 for c in s:
18 result = result + convert_letter(c)
19 return result
20
21 while True:
22 s = input('Enter string: ')
23 if is_letters_only(s):
24 break
25 print('String must have alphabetical letters only')
26
27 print(convert_string(s))

273

You might also like