Control Flow Testing

It was from the primeval wellspring of an antediluvian passion that my story arises which, like the round earth flattened on a map, is but a linear projection of an otherwise periphrastic and polyphiloprogenitive, non-planar, non-didactic, self-inverting construction whose obscurantist geotropic liminality is beyond reasonable doubt.

— Milinda Banerjee

Introduction

Control flow testing is one of two white box testing techniques. This testing approach identifies the execution paths through a module of program code and then creates and executes test cases to cover those paths. The second technique, discussed in the next chapter, focuses on data flow.

  Key Point

Path: A sequence of statement execution that begins at an entry and ends at an exit.

Unfortunately, in any reasonably interesting module, attempting exhaustive testing of all control flow paths has a number of significant drawbacks.

Even though control flow testing has a number of drawbacks, it is still a vital tool in the tester's toolbox.

Technique

Control Flow Graphs

Control flow graphs are the foundation of control flow testing. These graphs document the module's control structure. Modules of code are converted to graphs, the paths through the graphs are analyzed, and test cases are created from that analysis. Control flow graphs consist of a number of elements:

  Key Point

Control flow graphs are the foundation of control flow testing.

Process Blocks

A process block is a sequence of program statements that execute sequentially from beginning to end. No entry into the block is permitted except at the beginning. No exit from the block is permitted except at the end. Once the block is initiated, every statement within it will be executed sequentially. Process blocks are represented in control flow graphs by a bubble with one entry and one exit.

Decision Point

A decision point is a point in the module at which the control flow can change. Most decision points are binary and are implemented by if-then-else statements. Multi-way decision points are implemented by case statements. They are represented by a bubble with one entry and multiple exits.

Junction Point

A junction point is a point at which control flows join together.

The following code example is represented by its associated flow graph:

Figure 10-1: Flow graph equivalent of program code.

Levels of Coverage

In control flow testing, different levels of test coverage are defined. By "coverage" we mean the percentage of the code that has been tested vs. that which is there to test. In control flow testing we define coverage at a number of different levels. (Note that these coverage levels are not presented in order. This is because, in some cases, it is easier to define a higher coverage level and then define a lower coverage level in terms of the higher.)

Level 1

Even though statement coverage is the lowest level of coverage, even that may be difficult to achieve in practice. Often modules have code that is executed only in exceptional circumstances—low memory, full disk, unreadable files, lost connections, etc. Testers may find it difficult or even impossible to simulate these circumstances and thus code that deals with these problems will remain untested.

Holodeck is a tool that can simulate many of these exceptional situations. According to Holodeck's specification it "will allow you, the tester, to test software by observing the system calls that it makes and create test cases that you may use during software execution to modify the behavior of the application. Modifications might include manipulating the parameters sent to functions or changing the return values of functions within your software. In addition, you may also set error-codes and other system events. This set of possibilities allows you to emulate environments that your software might encounter - hence the name 'Holodeck.' Instead of needing to unplug your network connection, create a disk with bad sectors, corrupt packets on the network, or perform any outside or special manipulation of your machine, you can use Holodeck to emulate these problems. Faults can easily be placed into any software testing project that you are using with Holodeck."

Holodeck

To download Holodeck visit http://www.sisecure.com/holodeck/holodeck-trial.aspx.

Level 0

Level 2

Level 3

Level 4

Level 5

Level 7

Level 6

Before beginning control flow testing, an appropriate level of coverage should be chosen.

Structured Testing Basis Path Testing

No discussion on control flow testing would be complete without a presentation of structured testing, also known as basis path testing. Structured testing is based on the pioneering work of Tom McCabe. It uses an analysis of the topology of the control flow graph to identify test cases.

The structured testing process consists of the following steps:

Consider the following control flow graph:

Figure 10-7: An example control flow graph.

McCabe defines the Cyclomatic Complexity (C) of a graph as

C = edges - nodes + 2

Edges are the arrows, and nodes are the bubbles on the graph. The preceding graph has 24 edges and 19 nodes for a Cyclomatic Complexity of 24-19+2 = 7.

In some cases this computation can be simplified. If all decisions in the graph are binary (they have exactly two edges flowing out), and there are p binary decisions, then

C = p+1

Cyclomatic Complexity is exactly the minimum number of independent, nonlooping paths (called basis paths) that can, in linear combination, generate all possible paths through the module. In terms of a flow graph, each basis path traverses at least one edge that no other path does.

McCabe's structured testing technique calls for creating C test cases, one for each basis path.

  IMPORTANT !

Creating and executing C test cases, based on the basis paths, guarantees both branch and statement coverage.

Because the set of basis paths covers all the edges and nodes of the control flow graph, satisfying this structured testing criteria automatically guarantees both branch and statement coverage.

A process for creating a set of basis paths is given by McCabe:

  1. Pick a "baseline" path. This path should be a reasonably "typical" path of execution rather than an exception processing path. The best choice would be the most important path from the tester's view.

    Figure 10-8: The chosen baseline basis path ABDEGKMQS

  2. To choose the next path, change the outcome of the first decision along the baseline path while keeping the maximum number of other decisions the same as the baseline path.

    Figure 10-9: The second basis path ACDEGKMQS

  3. To generate the third path, begin again with the baseline but vary the second decision rather than the first.

    Figure 10-10: The third basis path ABDFILORS

  4. To generate the fourth path, begin again with the baseline but vary the third decision rather than the second. Continue varying each decision, one by one, until the bottom of the graph is reached.

    Figure 10-11: The fourth basis path ABDEHKMQS

    Figure 10-12: The fifth basis path ABDEGKNQS

  5. Now that all decisions along the baseline path have been flipped, we proceed to the second path, flipping its decisions, one by one. This pattern is continued until the basis path set is complete.

    Figure 10-13: The sixth basis path ACDFJLORS

    Figure 10-14: The seventh basis path ACDFILPRS

Thus, a set of basis paths for this graph are:

Structured testing calls for the creation of a test case for each of these paths. This set of test cases will guarantee both statement and branch coverage.

Note that multiple sets of basis paths can be created that are not necessarily unique. Each set, however, has the property that a set of test cases based on it will execute every statement and every branch.

Example

Consider the following example from Brown & Donaldson. It is the code that determines whether B&D should buy or sell a particular stock. Unfortunately, the inner workings are a highly classified trade secret so the actual processing code has been removed and generic statements like s1; s2; etc. have substituted for them. The control flow statements have been left intact but their actual conditions have been removed and generic conditions like c1 and c2 have been put in their place. (You didn't think we'd really show you how to know whether to buy or sell stocks, did you?)

  Note

s1, s2, ... represent Java statements while c1, c2, ... represent conditions.

boolean evaluateBuySell (TickerSymbol ts) { s1; s2; s3; if (c1) {s4; s5; s6;} else {s7; s8;} while (c2) { s9; s10; switch (c3) { case-A: s20; s21; s22; break; // End of Case-A case-B: s30; s31; if (c4) { s32; s33; s34; } else { s35; } break; // End of Case-B case-C: s40; s41; break; // End of Case-C case-D: s50; break; // End of Case-D } // End Switch s60; s61; s62; if (c5) {s70; s71; } s80; s81; } // End While s90; s91; s92; return result;

Figure 10-15: Java code for Brown & Donaldson's evaluateBuySell module.

The following flow diagram corresponds to this Java code:

Figure 10-16: Control flow graph for Brown & Donaldson's evaluateBuySell module.

The cyclomatic complexity of this diagram is computed by

edges - nodes + 2

or

22-16+2 = 8

Let's remove the code and label each node for simplicity in describing the paths.

Figure 10-17: Control flow graph for Brown & Donaldson's evaluateBuySell module.

A set of eight basis paths is:

  1. ABDP
  2. ACDP
  3. ABDEFGMODP
  4. ABDEFHKMODP
  5. ABDEFIMODP
  6. ABDEFJMODP
  7. ABDEFHLMODP
  8. ABDEFIMNODP

Remember that basis path sets are not unique; there can be multiple sets of basis paths for a graph.

This basis path set is now implemented as test cases. Choose values for the conditions that would sensitize each path and execute the tests.

Table 10-1: Data values to sensitize the different control flow paths.

Test Case

C1

C2

C3

C4

C5

1

False

False

N/A

N/A

N/A

2

True

False

N/A

N/A

N/A

3

False

True

A

N/A

False

4

False

True

B

False

False

5

False

True

C

N/A

False

6

False

True

D

N/A

False

7

False

True

B

True

False

8

False

True

C

N/A

True

Applicability and Limitations

Control flow testing is the cornerstone of unit testing. It should be used for all modules of code that cannot be tested sufficiently through reviews and inspections. Its limitations are that the tester must have sufficient programming skill to understand the code and its control flow. In addition, control flow testing can be very time consuming because of all the modules and basis paths that comprise a system.

Summary

Practice

  1. Below is a brief program listing. Create the control flow diagram, determine its Cyclomatic Complexity, choose a set of basis paths, and determine the necessary values for the conditions to sensitize each path.

if (c1) { while (c2) { if (c3) { s1; s2; if (c5) s5; else s6; break; // Skip to end of while else if (c4) { } else { s3; s4; break;} } // End of while } // End of if s7; if (c6) s8; s9; s10;

References

Beizer, Boris (1990). Software Testing Techniques (Second Edition). Van Nostrand Reinhold.

Myers, Glenford (1979). The Art of Software Testing. John Wiley & Sons.

Pressman, Roger S. (1982). Software Engineering: A Practitioner's Approach (Fourth Edition). McGraw-Hill.

Watson, Arthur H. and Thomas J. McCabe. Structured Testing: A Testing Methodology Using the Cyclomatic Complexity Metric. NIST Special Publication 500-235 available at http://www.mccabe.com/nist/nist_pub.php

Категории