Testing and Debugging: Programming Tools and Environments
Testing and Debugging: Programming Tools and Environments
Testing is difficult
People by nature assume that what they
do is correct
People by nature overlook minor
deficiencies in their own work
Easy to overlook or ignore bad results
Easy to choose only test cases that show
that the program works
Good to get someone elses help on this
Defensive programming
Anticipate potential problems and
design and code the system so that
they are avoided or at least detected
as early as possible
defensive design -- avoid bugs in the
first place
defensive coding -- take steps to localize
problems
Defensive design
Simplicity of design
Encapsulation
Design with error in mind
Analyze the design to identify problems
early
Make all assumptions and conditions
explicitly
Encapsulation
Minimize coupling (dependency) between
classesfewer places to check for a bug,
among other things.
Design so that the proper functionality
does not depend on other classes at the
same level of design
Design reviews
Show the design to another group:
management, other developers, or outside
consultants.
Writing/presenting a design teaches the
designer a lot by making more details explicit in
his/her mind.
Reviewers can provide a new viewpoint on the
design different implicit assumptions than the
original designer.
An example
DeriveExpr parse( istream&);
Takes an input stream corresponding to
one line of the input and returns the
expression tree corresponding to that
line.
Or throw an exception
Assertions
#include <assert.h>
assert(x=0);
causes program to abort with message
(typically, line number of file) if condition is not
true when assert line executed.
CC DNDEBUG test.cpp // turns off asserts
An
example
of
error
handling
class DeriveParserInfo {
private:
DeriveTokenStream token_stream;
DeriveExprFactor expr_factory;
DeriveToken cur_token;
public:
DeriveParserInfo(DeriveExprFactory);
DeriveExpr parse(istream&) throw(DeriveParseError);
//Preconditions: cur_token is set to the initial
token;
//Postconditions:
expr.
//
returns a non-NULL expr. or throws
DeriveParseError
// if the expr is not valid
}
{return
Evolutionary Programming
Compile your code often
Test your code before you implement
everything
Evolve your program by writing modules
and using stubs
Debugging techniques
Use a symbolic debugger (e.g., gdb, or, one
provided by your IDE) to locate problems
If a memory-checking tool is available learn
how to use it (to find dangling pointers,
memory leaks, etc.)
THINK
Keep an error log
Whenever you discover an error, check for the
same error in other parts of the program
Finding errors
Where is the error? What causes it?
Use inductive or deductive reasoning
Observations
One median calculation wrong.
All means correct.
Hypotheses
Mean calculation is correct.
Median calculation is wrong.
A bug we find
The median routine works by sorting the
array and returning the index of the
middle element, instead of the value of
the middle element
This fits all the symptoms, so it might be
the cause of our errors (instead of
another bug thats not the cause).
Order explanations by
probability
How difficult can it be to input integers? (1st
cause unlikely)
Not all the tests return wrong values. There are
no other values around that the routine could
mistakenly use to print instead of the right
answer. (3rd cause unlikely).
This leaves us with the second cause -- that the
computation itself is bad, as the most likely.
Order explanations by
(subjective) probability
A quick check of the code indicates that
the sum is initialized to 0.
A quick check of the code indicates that
the quotient is computed correctly.
So the iteration used to computed the
sum is probably wrong.
Actual cause
Iterator doesnt stop in time, goes
beyond end of array
Extra array element is usually zero
unless the memory has been previously
used.
Error logs
When was the error made?
Who made the error?
What was done incorrectly?
How could the error have been
prevented?
How was the error found?
How could the error have been detected
earlier and with less work?
Testing
Static testing
Code inspections
Walk throughs
Dynamic testing
Module testing
Integration testing
System testing
Regression testing (use the same test cases each time)
Concentrate
Use OO methods
Use a good programming language
Goals of Testing
Discover and prevent bugs, not show
that program works
The act of designing tests is one of the
best bug preventers known
Even tests are sometimes buggy
The real goal of testing is to reduce the
risk of failure to an acceptable level
Complete Testing
Complete testing is NOT possible for nontrivial software both practically and
theoretically
Assuming a program only has one input
of 10 characters, it would require 280
tests, which at 1microsecond/test would
take more than twice the current
estimated age of the universe
Test coverage
Statement coverage: each statement is
executed at least once
Decision coverage: every branch is taken
at least once
Test for invalid, unexpected conditions
Test for boundary conditions
Use varied tests
Regression testing
Every time new code is introduced/bugs
are fixed, all old test cases should still
produce the correct output
Every time a new test case uncovers a
bug, add it to your suit of test cases
Mutation testing
Testing technique that focuses on
measuring the adequacy of test cases
Should be used together with other
testing techniques
Based on the competent programmer
hypothesis: a programmer will create a
program, which if incorrect, is very close
to the correct program
Mutation Testing
Faults are introduced into the program by
creating many versions of the program called
mutants
Each mutant contains a single fault
Test cases are applied to the original program and
the mutant
The goal is to cause the mutant program to fail,
thus demonstrating the effectiveness of the test
case
if (x>y)
if (x<y)
mx = x;
else
mx = y;
return mx;
}
mx = x;
else
mx = y;
return mx;
}
Testing maxims
A successful test case is one that finds a
bug.
Always test your code thoroughly.
Mistakes in testing
mean/median code
Didnt compare to correct answer in test results
Didnt adequately test -- should cover all
possible executions
Regression testing
If someone gives you input which
produces the bug, make the input part of
your test suite after you fix the error.
Ensures that you dont reintroduce the
error in subsequent changes and bug
fixes (no going backwards).
Testing guidelines
A necessary part of a test case is the expected
output.
Avoid attempting to test your own programs.
Thoroughly inspect the results of each test.
Test cases must include the invalid and
unexpected.
Check that the program does not do what it is
not supposed to do.
gdb
gdb executable
Invoking gdb
Start debugging an executable
gdb executable*
Load a corefile
directory
*To look at source code, symbols,
etc., must be compiled with -g
Inspecting a corefile
You can look at any program that has
crashed (and produced a corefile) to see
any of its state at the time of the crash
Load executable and corefile into the
debugger
Use GDB's backtrace (bt) command to
see the call stack
list
list main
list 56
list 53,77
Breakpoints
A place where execution pauses, waits
for a user command
Can break at a function, a line number, or
on a certain condition
watch expr
Execution commands
run or r (run program from beginning)
run
run argList
start
kill
stops debugging
step or s
continue or cont
Examining data
print or p (print value)
print x
print x*y
print function(x)
printf
display (continuously display value)
undisplay (remove displayed value)
where (show current function stack)
set (change a value)
set n=3
Miscellaneous commands
help or h (display help text)
help
help step
help breakpoints