Testing, Testing, 1, 2, 3, 4, 5 . . .
In this lab we would like to solidify two concepts: code testing and
recursion. You will need a partner for this lab, preferably someone
that you do not usually talk to.
Testing
Thorough and rigorous testing is a required step when writing good
code (the opposite of unmaintainable code).
The question is, of course, "How do I test my code?"
There are a few things to remember when testing code:
- Everything that can go wrong eventually will go wrong.
- Check for errors whenever you read input from the user (even if
you only program for very smart users, eventually someone will be away
from their computer long enough for the cat to walk on the keyboard.
Always assume input is from that cat until proven otherwise.)
- When you test for errors, make sure that your error checking code
doesn't cause errors. This means you have to find a way to
run all of your error checking code, to make sure IT is free of bugs.
- Of course, you can write amazing, perfectly running, bug-free code
that is a true work of art, but if it is not what was requested it is
essentially useless. When you get a new assignment/project
statement/program specification, go through it very carefully and
write down everything that is required. If you are not sure about a
possible requirement, make sure to ask for clarification.
So remember to do AT LEAST the following:
- When reading input from the user, check that it is valid. If not,
do something about it (default values, loop back and get the input
again, etc).
- Before submitting your code, go through and try putting in bad
values everywhere you can.
- Make sure you have tested that every line of code runs
correctly. Make sure you have seen every case of every "if" statement
executed.
Testing Tools
Two tools that will help you immensely are diff and gdb.
Diff you have probably run into already during this course. It simply
takes two files and compares them. If they are not the same it prints
out what changes would have to be made to make one into the other.
For those of you familiar with Unix
Pipes, diff works great with stdin. We have covered diff a few
times already. Anyone that is not familiar with it yet should check
the man page for diff.
Another wonderful tool for testing is gdb, the most common
debugger for Linux. (Hopefully you remember using a debugger in CS
10. gdb gives you most of the power of the debugger in MSVC,
and a lot of other good features.) We will cover the use of
gdb next week, but if you have some time you may want to start
investigating how to work it.
Recursion
Recursion is a powerful programming technique where you let a function
solve some problem by calling itself so solve a slightly-easier
version of the same problem. Let's look at an example:
Suppose we want to write a function to calculate Fibonacci numbers
(the Fibonacci sequence is: 1, 1, 2, 3, 5, 8, 13, 21, 34 . . ., where
each number is calculated as the sum of the previous two numbers, and
the first two numbers are ones (1)).
int fib(int n)
We take in a number n, representing which Fibonacci number we want to
calculate, and return the value of that number. Thus fib(5)
would return 8 (if we behave like computer scientists and start
counting with 0). Let's look at two ways of writing this function:
// The iterative version
int fib(int n)
{
// Figure out the first two numbers
int fib1 = 1;
int fib2 = 1;
// If they asked for either of the first two numbers,
// we already know what the answer should be.
if (n <= 1) return 1;
// Otherwise, calculate
for (int i = 1; i < n; i++)
{
// Figure out the new number
int newNum = fib1 + fib2;
// Update
fib1 = fib2;
fib2 = newNum;
}
// Return the answer
return fib2;
}
Using the iterative approach, this is not too bad. There are a couple
of tricky bits here, like figuring to use a new variable when
calculating each new number in the series, and just how to do the
calculation, but overall it isn't too bad. Let us take a look at the
recursive solution.
// The recursive version
int fib(n)
{
// If it is one that we know to start with, return it
if (n <= 1) return 1;
// Otherwise, calculate the previous two and return the sum
return fib(n - 1) + fib(n - 2);
}
Admittedly, the recursive version is a little harder for some people
to understand. Still, it is a much more concise version that more
closely mimics the mathematical definition. Pretty neat, eh?
The Assignment
7 points:
Find a partner. WITHOUT COLLABORATING, each of you should
write up a solution to the following problem:
Write a program using recursion that calculates the factorial function
(!). (Remember that n! = n * (n-1) * (n-2) * ... 1.) On startup it
should print "Factorial Solver". It should then loop until EOF,
prompting the user for input with the prompt "n (1-10): ", and then
calculate the factorial of n, and print out the result.
When you and your partner are both done, show the TA. Once they give
you permission, swap programs (use Pine to email each other your code
as an attachment), and see if you can find a way to break
the other person's program. Use the following sample input and output files and
diff to see if they followed the directions rigorously.
When you are done, explain your findings to your partner. Turn in
a directory containing your code as "lab4.cc", theirs as "partner.cc",
and a text file containing what you tested, and any comments you have
about their code (style and functionality comments are OK.) Your
comments should be "comments.txt".
.2 points:
Edit "comments.txt": does your partner's code meet the course style
guidelines?
.2 points:
Add to "comments.txt": Describe a method for using the sample input
and output files and diff to test your partner's program. You may
want to look at the assignment pages for this quarter so far.
.2 points:
What is the largest integer n such that n! fits in a
signed integer? Is this larger or smaller than you expected?
.2 points:
Add the following line to the beginning of your code. Save it, close
it, and reopen it under Emacs. Re-indent everything. What does this do?
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 3 -*- */
.2 points:
Write a program that uses a recursive function to compute integer
powers. It should take in two inputs, x and n, and return x^n. Submit
this program as "power.cc".
.2 points:
The recursive solution to the Fibonacci problem that we presented
in this lab is very inefficient. Why? (HINT: Write out the functions
that have to be called to solve fib(5))
.2 points:
Using gdb, set a breakpoint in the first line of the recursive
solution to the Fibonacci function. How many times does gdb think the
function is called in fib(5)?
.2 points:
What options do you have to add to your .emacs file to set the tab
width to 3 characters?