Friday, May 1, 2009

White Box Testing

White Box Testing
What is WBT? White box testing involves looking at the structure of the code. When you know the internal structure of a product, tests can be conducted to ensure that the internal operations performed according to the specification. And all internal components have been adequately exercised. In other word WBT tends to involve the coverage of the specification in the code.
Code coverage is defined in following types as listed below:
1. Segment coverage – Each segment of code b/w control structure is executed at least once.
2. Branch Coverage or Node Testing – Each branch in the code is taken in each possible direction at least once.
3. Compound Condition Coverage – When there are multiple conditions, you must test not only each direction but also each possible combinations of conditions, which is usually done by using a ‘Truth Table’
4. Basis Path Testing – Each independent path through the code is taken in a pre-determined order.
5. Data Flow Testing (DFT) – In this approach you track the specific variables through each possible calculation, thus defining the set of intermediate paths through the code i.e., those based on each piece of code chosen to be tracked. Even though the paths are considered independent, dependencies across multiple paths are not really tested for by this approach. DFT tends to reflect dependencies but it is mainly through sequences of data manipulation. This approach tends to uncover bugs like variables used but not initialize, or declared but not used, and so on.
6. Path Testing – Path testing is where all possible paths through the code are defined and covered. This testing is extremely laborious and time consuming.
7. Loop Testing – In addition top above measures, there are testing strategies based on loop testing. These strategies relate to testing single loops, concatenated loops, and nested loops. Loops are fairly simple to test unless dependencies exist among the loop or b/w a loop and the code it contains.
What do we do in WBT?
In WBT, we use the control structure of the procedural design to derive test cases. Using WBT methods a tester can derive the test cases that
- Guarantee that all independent paths within a module have been exercised at least once.
- Exercise all logical decisions on their true and false values.
- Execute all loops at their boundaries and within their operational bounds
- Exercise internal data structures to ensure their validity.
White box testing (WBT) is also called Structural or Glass box testing.
Why WBT?
We do WBT because Black box testing is unlikely to uncover numerous sorts of defects in the program. These defects can be of the following nature:
- Logic errors and incorrect assumptions are inversely proportional to the probability that a program path will be executed. Error tend to creep into our work when we design and implement functions, conditions or controls that are out of the program
- The logical flow of the program is sometimes counterintuitive, meaning that our unconscious assumptions about flow of control and data may lead to design errors that are uncovered only when path testing starts.
- Typographical errors are random, some of which will be uncovered by syntax checking mechanisms but others will go undetected until testing begins.
Skills Required
1. Talking theoretically, all we need to do in WBT is to define all logical paths, develop test cases to exercise them and evaluate results i.e. generate test cases to exercise the program logic exhaustively.
2. For this we need to know the program well i.e. We should know the specification and the code to be tested; related documents should be available too us .We must be able to tell the expected status of the program versus the actual status found at any point during the testing process.
Limitations
Unfortunately in WBT, exhaustive testing of a code presents certain logistical problems. Even for small programs, the number of possible logical paths can be very large. For instance, a 100 line C Language program that contains two nested loops executing 1 to 20 times depending upon some initial input after some basic data declaration. Inside the interior loop four if-then-else constructs are required. Then there are approximately 1014 logical paths that are to be exercised to test the program exhaustively. Which means that a magic test processor developing a single test case, execute it and evaluate results in one millisecond would require 3170 years working continuously for this exhaustive testing which is certainly impractical. Exhaustive WBT is impossible for large software systems. But that doesn’t mean WBT should be considered as impractical. Limited WBT in which a limited no. of important logical paths are selected and exercised and important data structures are probed for validity, is both practical and WBT. It is suggested that white and black box testing techniques can be coupled to provide an approach that that validates the software interface selectively ensuring the correction of internal working of the software.
Tools used for White Box testing:
Few Test automation tool vendors offer white box testing tools which:
1) Provide run-time error and memory leak detection;
2) Record the exact amount of time the application spends in any given block of code for the purpose of finding inefficient code bottlenecks; and
3) Pinpoint areas of the application that have and have not been executed.
Basis Path Testing : Basis path testing is a white box testing technique first proposed by Tom McCabe. The Basis path method enables to derive a logical complexity measure of a procedural design and use this measure as a guide for defining a basis set of execution paths. Test Cases derived to exercise the basis set are guaranteed to execute every statement in the program at least one time during testing.
Flow Graph Notation: The flow graph depicts logical control flow using a diagrammatic notation. Each structured construct has a corresponding flow graph symbol.
Cyclomatic Complexity: Cyclomatic complexity is a software metric that provides a quantitative measure of the logical complexity of a program. When used in the context of a basis path testing method, the value computed for Cyclomatic complexity defines the number for independent paths in the basis set of a program and provides us an upper bound for the number of tests that must be conducted to ensure that all statements have been executed at least once.
An independent path is any path through the program that introduces at least one new set of processing statements or a new condition.
Computing Cyclomatic Complexity: Cyclomatic complexity has a foundation in graph theory and provides us with extremely useful software metric. Complexity is computed in one of the three ways:
1. The number of regions of the flow graph corresponds to the Cyclomatic complexity.
2. Cyclomatic complexity, V(G), for a flow graph, G is defined asV (G) = E-N+2 Where E, is the number of flow graph edges, N is the number of flow graph nodes.
3. Cyclomatic complexity, V (G) for a flow graph, G is also defined as:V (G) = P+1 where P is the number of predicate nodes contained in the flow graph G.
Graph Matrices: The procedure for deriving the flow graph and even determining a set of basis paths is amenable to mechanization. To develop a software tool that assists in basis path testing, a data structure, called a graph matrix can be quite useful.A Graph Matrix is a square matrix whose size is equal to the number of nodes on the flow graph. Each row and column corresponds to an identified node, and matrix entries correspond to connections between nodes.
Control Structure Testing: Described below are some of the variations of Control Structure Testing:
Condition Testing: Condition testing is a test case design method that exercises the logical conditions contained in a program module.
Data Flow Testing: The data flow testing method selects test paths of a program according to the locations of definitions and uses of variables in the program.
Loop Testing: Loop Testing is a white box testing technique that focuses exclusively on the validity of loop constructs. Four classes of loops can be defined: Simple loops, Concatenated loops, nested loops, and unstructured loops.
Simple Loops: The following sets of tests can be applied to simple loops, where ‘n’ is the maximum number of allowable passes through the loop.
1. Skip the loop entirely.
2. Only one pass through the loop.
3. Two passes through the loop.
4. ‘m’ passes through the loop where m is less than n.
5. n-1, n, n+1 passes through the loop.
Nested Loops: If we extend the test approach from simple loops to nested loops, the number of possible tests would grow geometrically as the level of nesting increases.
1. Start at the innermost loop. Set all other loops to minimum values.
2. Conduct simple loop tests for the innermost loop while holding the outer loops at their minimum iteration parameter values. Add other tests for out-of-range or exclude values.
3. Work outward, conducting tests for the next loop, but keep all other outer loops at minimum values and other nested loops to "typical" values.
4. Continue until all loops have been tested.
Concatenated Loops: Concatenated loops can be tested using the approach defined for simple loops, if each of the loops is independent of the other. However, if two loops are concatenated and the loop counter for loop 1 is used as the initial value for loop 2, then the loops are not independent.
Unstructured Loops: Whenever possible, this class of loops should be redesigned to reflect the use of the structured programming constructs.

Unit Test Case
Below are some usefull tips:
Input values: Write test cases for each of the identified inputs (positive & negative) accepted by the Unit.Expected Functionality: Cover each functionality that is expected to be in the Unit.Output values: Write test cases which will produce all types of output values that are expected from the module / unit.Path coverage: If the Unit have conditional processing that results in various paths, then write test cases to cover each of these paths.Abnormal terminations: Behavior of the Unit in case of abnormal termination should be tested.Error messages: Check error messages / warnings. These should be short, precise and self-explanatory. They should be properly phrased and free of grammatical mistakes. Screen Layout: Web page or screen layout must be tested against the requirements. Ensure that pages and screens are consistent and as per requirements.If you are testing database application, it is important to make sure that transactions are properly designed and no way inconsistent data gets saved in the database.Also, please note that the format of the unit test case document can be similar to the format of normal functional test cases. The fields can be:
Test Case Id
Test Case Purpose
Procedure / steps to be performed
Input Value / Test Data
Expected Result
Actual Result
Remarks.

Testing at Programming / Coding phase
Check the code for consistency with design - The areas to check include modular structure, module interfaces, data structures, functions, algorithms and I/O handling.
Start testing with organized and systematic manner - Perform the Testing process in an organized and systematic manner with test runs dated, annotated and saved. A plan or schedule can be used as a checklist to help the programmer organize testing efforts. If errors are found and changes made to the program, all tests involving the erroneous segment (including those which resulted in success previously) must be rerun and recorded.
Asks some colleague for assistance - Some independent party, other than the programmer of the specific part of the code, should analyze the development product at each phase. The programmer should explain the product to the party who will then question the logic and search for errors with a checklist to guide the search. This is needed to locate errors the programmer has overlooked.
Use available tools - The programmer should be familiar with various compilers and interpreters available on the system for the implementation language being used because they differ in their error analysis and code generation capabilities.
Apply Stress to the Program - Testing should exercise and stress the program structure, the data structures, the internal functions and the externally visible functions or functionality. Both valid and invalid data should be included in the test set.
Test one at a time - Pieces of code, individual modules and small collections of modules should be exercised separately before they are integrated into the total program, one by one. Errors are easier to isolate when the no. of potential interactions should be kept small. Instrumentation-insertion of some code into the program solely to measure various program characteristics – can be useful here. A tester should perform array bound checks, check loop control variables, determine whether key data values are within permissible ranges, trace program execution, and count the no. of times a group of statements is executed.
Measure testing coverage/When should testing stop? - If errors are still found every time the program is executed, testing should continue. Because errors tend to cluster, modules appearing particularly error-prone require special scrutiny.
Use Metrics - The metrics used to measure testing thoroughness include statement testing (whether each statement in the program has been executed at least once), branch testing (whether each exit from each branch has been executed at least once) and path testing (whether all logical paths, which may involve repeated execution of various segments, have been executed at least once). Statement testing is the coverage metric most frequently used as it is relatively simple to implement.
The amount of testing depends on the cost of an error. Critical programs or functions require more thorough testing than the less significant functions.


Top down Testing vs Bottom up Testing

Top down Testing: In this approach testing is conducted from main module to sub module. if the sub module is not developed a temporary program called STUB is used for simulate the submodule.
Advantages:
- Advantageous if major flaws occur toward the top of the program.- Once the I/O functions are added, representation of test cases is easier.- Early skeletal Program allows demonstrations and boosts morale.

Disadvantages:- Stub modules must be produced- Stub Modules are often more complicated than they first appear to be.- Before the I/O functions are added, representation of test cases in stubs can be difficult.- Test conditions ma be impossible, or very difficult, to create.- Observation of test output is more difficult.- Allows one to think that design and testing can be overlapped.- Induces one to defer completion of the testing of certain modules.

Bottom up testing: In this approach testing is conducted from sub module to main module, if the main module is not developed a temporary program called DRIVERS is used to simulate the main module.
Advantages:
- Advantageous if major flaws occur toward the bottom of the program.- Test conditions are easier to create.- Observation of test results is easier.

Disadvantages:
- Driver Modules must be produced.- The program as an entity does not exist until the last module is added.
Stubs and Drivers
It is always a good idea to develop and test software in "pieces". But, it may seem impossible because it is hard to imagine how you can test one "piece" if the other "pieces" that it uses have not yet been developed (and vice versa).
A software application is made up of a number of ‘Units’, where output of one ‘Unit’ goes as an ‘Input’ of another Unit. e.g. A ‘Sales Order Printing’ program takes a ‘Sales Order’ as an input, which is actually an output of ‘Sales Order Creation’ program.
Due to such interfaces, independent testing of a Unit becomes impossible. But that is what we want to do; we want to test a Unit in isolation! So here we use ‘Stub’ and ‘Driver.
A ‘Driver’ is a piece of software that drives (invokes) the Unit being tested. A driver creates necessary ‘Inputs’ required for the Unit and then invokes the Unit.
Driver passes test cases to another piece of code. Test Harness or a test driver is supporting code and data used to provide an environment for testing part of a system in isolation. It can be called as as a software module which is used to invoke a module under test and provide test inputs, control and, monitor execution, and report test results or most simplistically a line of code that calls a method and passes that method a value.
For example, if you wanted to move a fighter on the game, the driver code would bemoveFighter(Fighter, LocationX, LocationY);
This driver code would likely be called from the main method. A white-box test case would execute this driver line of code and check "fighter.getPosition()" to make sure the player is now on the expected cell on the board.
A Unit may reference another Unit in its logic. A ‘Stub’ takes place of such subordinate unit during the Unit Testing.
A ‘Stub’ is a piece of software that works similar to a unit which is referenced by the Unit being tested, but it is much simpler that the actual unit. A Stub works as a ‘Stand-in’ for the subordinate unit and provides the minimum required behavior for that unit. A Stub is a dummy procedure, module or unit that stands in for an unfinished portion of a system.
Four basic types of Stubs for Top-Down Testing are:
- Display a trace message- Display parameter value(s)- Return a value from a table- Return table value selected by parameter
A stub is a computer program which is used as a substitute for the body of a software module that is or will be defined elsewhere or a dummy component or object used to simulate the behavior of a real component until that component has been developed.
For example, if the movefighter method has not been written yet, a stub such as the one below might be used temporarily – which moves any player to position 1.
public void moveFighter(Fighter player, int LocationX, int LocationY)
{fighter.setPosition(1);}
Ultimately, the dummy method would be completed with the proper program logic. However, developing the stub allows the programmer to call a method in the code being developed, even if the method does not yet have the desired behavior.
Programmer needs to create such ‘Drivers’ and ‘Stubs’ for carrying out Unit Testing.
Both the Driver and the Stub are kept at a minimum level of complexity, so that they do not induce any errors while testing the Unit in question.
Stubs and drivers are often viewed as throwaway code. However, they do not have to be thrown away: Stubs can be "filled in" to form the actual method. Drivers can become automated test cases.
Example - For Unit Testing of ‘Sales Order Printing’ program, a ‘Driver’ program will have the code which will create Sales Order records using hardcoded data and then call ‘Sales Order Printing’ program. Suppose this printing program uses another unit which calculates Sales discounts by some complex calculations. Then call to this unit will be replaced by a ‘Stub’, which will simply return fix discount data.

3 comments: