testing and debugging hakam alomari [email protected]
TRANSCRIPT
Outline
• Include Guards• Namespace name access• Testing Concepts– What is software testing?– How to find faults?– Black Box vs. Glass Box testing– How many tests cases are there?– Regression testing
• Using assert and cerr• Building test cases, Makefile
Include Guards
• Are used to prevent the contents of a file from being included more than once– Example: (string.h)
#ifndef CS2_STRING_H#define CS2_STRING_HClass string {
//…
};#endif
Example [wiki]
File "grandfather.h“struct foo {
int member;
};
File "father.h“#include "grandfather.h"
File "child.c“#include "grandfather.h" #include "father.h"
Use of #include guards
File "grandfather.h“#ifndef GRANDFATHER_H #define GRANDFATHER_H struct foo {
int member;
}; #endif
File "father.h“#include "grandfather.h"
File "child.c“#include "grandfather.h" #include "father.h"
Namespaces• Used to group entities (e.g., objects) under a name, such as each
group has its own name• Example:
namespace foo1 {int var = 1;
}namespace foo2 {
int var = 2;
}
int main () {std::cout<< foo1::var <<“\n”; // output 1std::cout<< foo2::var <<“\n”; // output 2return 0;
}
Standard Includes
• Names defined by standard includes (e.g., cout) can be used as: – Prefix the name with std::– Use using std::name before the name is used– Put using namespace::std before the name is used– Example:
int main() { std::cout << "Hello World!\n"; // ...
int main() { using std::cout; cout << "Hello World!\n"; // ...
using namespace std; int main() { cout << "Hello World!\n"; // ...
Software Testing
• Software testing is used to find errors • error:– Incorrect output for a given input– Caused by faults inside the program– A fault is represent the incorrect part of the code• E.g., incorrect stopping condition
• So, test cases should be developed to exercise the program and uncovering errors
Testing levels
• Start by testing each method (unit tests)• Then each class in full (module tests)• Then the whole program (system tests)
• The test case that has a high probability to uncover an error is known to be the best one
Testing Types
• Black box vs Glass box (white box)– Black box• Uses only the I/O spec. to develop test cases
– Glass box • Uses only the implementation details to develop test
cases
• Both types are necessary to develop a good set of test cases
Number of Test Cases
• Functions usually have a very large number of pre. and post. conditions
• So, there is no need to test all of these to make sure our function behaves correctly
• How?– Pairing down test cases, by:– Develop equivalence classes of test cases– Examine the boundaries of these classes carefully
Equivalence Classes
• The input and output often fall into equivalence partitions where the system behaves in an equivalent way for each class
• Then, the test cases should be developed to test each partition
Classes Boundaries
• Example:– Partition system inputs and outputs into
equivalence classes– If input is a 2 digit integer between 10 and 99
then the equivalence partitions are < 10 (invalid), 10 – 99 (valid), and > 99 (invalid)
– Choose test cases at the boundary of these partitions
– 09, 10, 99, 100
Building Test Cases
1. Determine the I/O spec. for the method2. Develop test cases3. Method implementation 4. Run the method against the test cases
from 25. Debugging (fix)6. Go to 4
Test Steps• There are three steps in a test:
– Set-up the "fixture", the system to be tested– Perform the test– Verify the results, make sure there are no unwanted side effects
• Example: Test for existence of const char& operator[](int) const – // Setup fixture
• const string str("abc"); – // Test
• assert(str[0] == 'a'); • assert(str[1] == 'b'); • assert(str[2] == 'c');
– // Verify • assert(str.length() == 3);
Regression Testing
• The intent of regression testing is to ensure that a change, such as a bugfix, did not introduce new faults
• Each time you add a new method to your class or fix a fault run your test cases (all of them)– Adding something new or fixing a problem may
have side effects• Re-running your test cases will uncover these
problems
Asserts
• Used to output an error message and terminate a program if a condition is false
• Asserts are useful for testing• #include <cassert> is required to use asserts• Example:– assert(result == correct_result);
• Asserts can be turned off using– #define NDEBUG
Debugging
• Used when the program is not working properly • One easy thing to do is output the value of relevant variables• Use cerr instead of cout. cout is buffered• Buffering means output is accumulated in a "buffer" and the
accumulated output sent out to the OS together• Output to cerr is not buffered, it is output immediately• If a program crashes there may be output in a buffer that gets
lost• It is a good idea to include a message with values that are
output– std::cerr << "var1 = " < < var1 << "\n";
The make Command
• The make command may be used to automate compiling
• The make command when executed will look for a file named makefile and then, if not found, for a file named Makefile to specify dependencies
• Otherwise a makefile must be specified with the -f option
• The make command has some rules built-in so it can do some basic compiling but usually the developer provides a makefile
Makefile Rules
• Rules have the form:– target: dependency_list – TAB command
• Target A file or name• dependency_list Files that the target "depends on"• TAB The TAB character. REQUIRED!• Command Typically a compiling/linking command
but can be any command. There may be more than one TAB/command line
• If the modification time of any dependency is more recent than the target the command lines are executed
Common Rule Types
• Creating machine language, .o, files and linking are two things that must be done# Link executable: file_1.o file_2.o
g++ -Wall file_1.o file_2.o -o executable
# Create .o file file.o: file.h file.cpp
g++ -Wall -c file.cpp
• Note .cpp files are typically not targets, .cpp files do not depend on other files
Makefile Example# Create the executable is the first rule here # Link main: bigint.o main.o
g++ -Wall bigint.o main.o -o main
# Create .o file main.o: bigint.h main.cpp
g++ -Wall -c main.cpp
# Create .o file bigint.o: bigint.h bigint.cpp
g++ -Wall -c bigint.cpp
# Remove .o and executable clean:
rm -f *.o rm -f main
References
• [wiki]: http://en.wikipedia.org/wiki/Include_guard
• Computer Science II Lab: http://classes.cs.kent.edu/courses/cs33001_lab/svn/index.html
• Software Testing for CS II slides: http://www.cs.kent.edu/~jmaletic/CS33001/