testing and debugging hakam alomari [email protected]

23
Testing and Debugging Hakam Alomari [email protected]

Upload: lisa-pearson

Post on 23-Dec-2015

226 views

Category:

Documents


1 download

TRANSCRIPT

Testing and Debugging

Hakam [email protected]

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/