and black-box techniques for class-level testing object-oriented ...

1 downloads 192 Views 118KB Size Report
process, different concepts used to develop software can im- ply different strategies and techniques. For instance, object- oriented development has other ...
INTEGRATING WHITE- AND BLACK-BOX TECHNIQUES FOR CLASS-LEVEL TESTING OBJECT-ORIENTED PROTOTYPES∗ VOLKER GRUHN University of Dortmund Computer Science Department Software-Technology 44221 Dortmund, Germany [email protected]

SAMI BEYDEDA University of Dortmund Computer Science Department Software-Technology 44221 Dortmund, Germany [email protected]

ABSTRACT

model testing is carried out as one phase during the development, whereas in prototyping the testing phase is repeated for each developed prototype. Even in the same software process, different concepts used to develop software can imply different strategies and techniques. For instance, objectoriented development has other requirements than structured development. Testing techniques should also be suited to the needs of the application domain, especially with respect to quality of deployed software. Generally, a high degree of quality can only be achieved by intensive testing. Although one can argue that any type of software should be as qualitative as possible, cost-effectiveness limits the effort spent for testing. We assume an application domain in which software failures can have serious consequences and which therefore require a high degree of software quality. In the second section of this article we discuss strategies for testing object-oriented prototypes. The third section describes our approach to class-level testing objectoriented prototypes using a demonstrative example. Finally, the fourth section presents our conclusions.

An important activity in software development is the testing of developed software using the appropriate techniques. Obviously, these techniques have to take into account the type of the software process and the type of the developed software. This article proposes a technique for testing classes developed during a special type of software process, namely object-oriented prototyping. Furthermore, the technique developed is suited for application domains requiring a high degree of quality of the deployed software. The main idea of our approach is to interpret each prototyping iteration as a correction of the software, similar to corrections taken during the maintenance phase. With this interpretation in mind, we tried to adopt regression testing techniques for testing prototype classes. Unfortunately, all regression testing techniques we have studied have some shortcomings limiting their applicability to testing prototypes. Therefore, we have developed a new technique that integrates two existing techniques resulting in one which can be used for both white- and black-box testing.

Keywords

2

STRATEGIES FOR CLASS-LEVEL TESTING PROTOTYPES 2.1 Simple Strategies

Integrated white- and black-box testing, class-level testing, prototyping, regression testing.

1

INTRODUCTION

Although various techniques and tools have been proposed aiming to automate software development, it still remains a creative thus human intensive process. Generally, human intensive processes have the shortcoming that the quality of the products obtained heavily depend on issues such as knowledge, experience and motivation of the persons involved. Therefore, a testing phase within such a process, which should be at best conducted by people other those involved in producing the products, can help to ensure a certain degree of quality. In software development, it is obvious that the testing phase should be suited to the type of the software process. For instance, a software process according to the waterfall model [11] requires other strategies and techniques for testing than a prototyping-based process [2, 3]. In the waterfall

Prototyping can have various objectives. The most important one is to capture user requirements, called explorative prototyping. But there are also other objectives distinguishing prototyping in experimental and evolutionary [5]. The focus of this article is on evolutionary prototyping which is also called incremental or evolutionary software development. Evolutionary prototyping refers to the process of developing software through evolving an initial version. Reasons to evolve software could be a changed environment or changed user requirements. Evolutionary prototyping can also be used to cope with complexity. A problem which is too complex to be implemented in one pass can be divided into subproblems which can be implemented by incrementally extending an initial prototype until it covers the whole problem. An important question concerns testing of the various versions of the prototype developed throughout each proto-

∗ This work has been supported by ESPRIT Project Process Instance Evolution (PIE) under sign 34840.

1

typing iteration. Obviously, each version should be tested. But it is not clear which properties concerning the implementation and the specification require testing, because successive versions usually share properties which have to be tested only once. For instance, prototyping the user interface of a particular system has no influence on the functionality of the system. This means testing of its functionality is not required. Two simple strategies are applicable to testing prototypes: 1. The first strategy treats each version of the prototype like an isolated software product. Thus, each version is tested without any consideration of tests made before. But, as mentioned above, successive versions may have common properties in the implementation or the specification which have to be tested only once. This strategy would imply testing of the common properties in every prototyping iteration, resulting in a waste of resources. 2. The second strategy requires only the testing of changed parts in the implementation or the specification, assuming unchanged parts are sufficiently tested beforehand. But it ignores the fact that changes in some parts of a prototype can affect other unchanged parts through data and control dependencies. Thus, this strategy does not fulfill the requirement of high degree of software quality.

2.2

An Optimal Strategy

An optimal strategy should not only require the testing of sections in the implementation which have been changed, but also the testing of unchanged sections which are affected by changes within other sections. A modified section in the source code can affect other sections either through control or data dependencies. A simple example for a control dependency is an if-then-else construct. The two alternatives following then and else are executed dependent on the predicate following if. A change in the predicate could result in the execution of the wrong alternative. The program would calculate a wrong result, although both alternatives are correctly implemented. A data dependency is given between the definition and the use of a variable. An error in the definition of a variable could lead to the calculation of a wrong result, although the equations for the calculation are correct. Since testing of software cannot reveal errors in the specification, other techniques like Fagan inspection [4] have to be applied for the specification to ensure that the modifications are made as intended. Changes to the source code of a program should be made after validating the specification to avoid changing the source code according to an incorrect specification. Therefore, we assume that the specification is modified correctly. The main property of prototyping is that the functionality of the prototype is modified or extended in each prototyping iteration. Therefore, missing functionality can be interpreted as a fault which has to be corrected. This means each

prototyping iteration corresponds to a ‘correction’ of the prototype. With this interpretation in mind, prototypes can be considered as different releases of a program and a test of a prototype as a regression test of the corrected program. In addition to the requirement for high degree of software quality, a safe regression testing technique for classes, the technique of Rothermel et al. [10], was investigated for its applicability on prototypes. As this regression testing technique does not consider the specification of the prototype class under test, it is integrated with a black-box testing technique, the technique of Hong et al. [7], to a new technique.

3

A TECHNIQUE FOR CLASS-LEVEL TESTING PROTOTYPES 3.1 Integrated White- and Black-Box Testing Usually, sufficiently testing requires both, testing through white- and black-box techniques. Although both tasks are similar in that they have the same objective, namely detecting faults within a program, often white- and black-box techniques are applied separately using different tools. The reason for this is the lack of techniques and tools integrating both tasks. An integrated technique supported by a single tool can have several benefits: 1. The tester has only to be familiar with the concepts underlying one technique and needs training only for one tool. 2. Less maintenance effort is required, since only one tool has to be maintained. 3. Testing can be carried out more efficiently, since an integrated technique can generate test cases covering both the specification and the source code at the same time. Although an integrated techniques can have such benefits, only few investigated the integration of white- and blackbox testing techniques for object-oriented software [1].

3.2

A Demonstrative Example

In the remainder of this article, we use a simple example to demonstrate the underlying concepts of the testing technique. The example consists of a class representing a special kind of financial instrument called option. An option gives the holder the right, but not the obligation, to buy (call option) or sell (put option) a specified amount of the underlying at a specified price at a certain time in future [8]. The underlying of an option can be a financial instrument, such as a stock or a bond, or a commodity, such as gold or coffee. The holder has to pay a premium for this right to the underwriter of the option. Options are distinguished into European and American style options. A European style option is one that may only be exercised on its expiry date, while an American style option is one that may be exercised at any time prior to expiry. Usually the price, which is also called strike, is fixed once and remains constant until the expiry of the option. But there are also options allowing adjustments of the strike, called ladder options.

s0 t3 t4

t1 t2

t5 t6 t 12

t7 t8 t9

in the money

t 10 t 11 t 16

at the money

t 13 t 14 t 17

out of the money

t 15

t 18 t 19

t 20

t 21

sf = (s0 , in, option(), #strike = 1 ∧ s.price > strike, init) = (s0 , in, option(), #strike > 1 ∧ s.price > strikeini , init) = (s0 , at, option(), #strike = 1 ∧ s.price = strike, init) = (s0 , at, option(), #strike > 1 ∧ s.price = strikeini , init) = (s0 , out, option(), #strike = 1 ∧ s.price < strike, init) = (s0 , out, option(), #strike > 1 ∧ s.price < strikeini , init) = (in, out, s.changepr(dpr), s.price + dpr < strike, {s.price = s.price + dpr}) t8 = (in, out, update strike(), style = ladder ∧ strikecur < s.price < strikenext , {locked prof it = abs(strikenext − strikecur ), strikecur = strikenext }) t9 = (in, in, pay off(), exerciseable, {return abs(s.price − strike) + locked prof it}) t10 = (in, at, s.changepr(dpr), s.price + dpr = strike, {s.price = s.price + dpr}) t11 = (in, at, update strike(), style = ladder ∧ strikecur < s.price = strikenext , {locked prof it = abs(strikenext − strikecur ), strikecur = strikenext }) t12 = (at, at, pay off(), exerciseable, {return locked prof it}) t13 = (at, out, s.changepr(dpr), s.price + dpr < strike, {s.price = s.price + dpr}) t14 = (at, out, update strike(), style = ladder ∧ strikecur = s.price < strikenext , {locked prof it = abs(strikenext − strikecur ), strikecur = strikenext }) t15 = (out, out, pay off(), exerciseable, {return locked prof it}) t16 = (at, in, s.changepr(dpr), s.price + dpr > strike, {s.price = s.price + dpr}) t17 = (out, at, s.changepr(dpr), s.price + dpr = strike, {s.price = s.price + dpr}) t18 = (out, in, s.changepr(dpr), s.price + dpr > strike, {s.price = s.price + dpr}) t19 = (in, sf , ˜option(), true, ∅) t20 = (at, sf , ˜option(), true, ∅) t21 = (out, sf , ˜option(), true, ∅)

t1 t2 t3 t4 t5 t6 t7

Figure 1: Specification of class option

An option may be in different states dependent on the relation between its strike and the current price of the underlying. A call (put) option is said to be in-the-money if the strike is less (greater) than the current price, at-the-money is the strike is equal to the current price, and out-of-the-money if the strike is greater (less) than the current price of the underlying. The holder of an option only exercises the option if it is in-the-money. In this case, the holder of a call (put) option can gain a pay-off equal to s.price − strike (strike − s.price). In all other states the option would not be exercised, because it would be more profitable to buy or to sell the underlying at the current price on the spot market.

3.3

Specification and Implementation of Class option Figure 1 shows the specification of class account

in form of a class state machine (CSM) [7]. In this figure each state of class account is represented by a circle, while each transition is depicted through an arrow leading from its source state to its target state. These transitions are formally specified through 5-tuples (source, target, event, guard, action) below this figure. A transition consists – besides of a source and a target state – also of an event causing the transition, a predicate guard which has to be fulfilled before the transition can occur, and an action defining operations on the attributes during the transition. There are also two special circles labeled s0 and sf . These two circles represent the state of an object before its creation and after its destruction, respectively. Thus, they represent states in which the attributes and their values are not defined, meaning that these two states are no concrete states of the object. Furthermore, a CSM also possesses an error state indicating that an error has occurred. Since an error state is not required for further explanations and the example should not be unnecessarily complicated, the error state and the corresponding error transitions are omitted. Note that in the above figure, all equations within the guards are given for call options because of the duality between call and put options. For instance, the guard of transition t1 requires that the strike of a call option has to be less than the current price s.price of the underlying. But this guard is also fulfilled for put options having a strike which is greater than the price of the underlying. Furthermore, the guards of transition t9 , t12 and t15 state that the option has to be exerciseable before the corresponding transition can occur. Options are only exerciseable at certain times prior or at its maturity, as explained before. The source code of the class option, which is built according to the specification in figure 1, is given in figure 2.

3.4

Class Control/Data Flow Graph

Our method proposed for integrated white- and blackbox testing operates on a graphical representation of the class called class control/data flow graph (CCDFG) in correspondence to CCFGs of Rothermel et al.. Actually, the CCDFG of a class is an extension of the corresponding CCFG. A CCDFG is a directed graph visualizing both control and data flow within a class. It consists of flow graphs for each method of a class. Each statement of a method is represented by a node which is connected to control flow edges and may also be connected to data flow edges. The number of outgoing control flow edges can either be one or two, depending on the statement represented by a node. A node representing a predicate statement has two outgoing edges labeled t (true) and f (f alse) to indicate the path which is to be taken corresponding to the result of the comparison, whereas all other nodes representing other statements have only one outgoing control flow edge1 . The number of outgoing data flow edges can vary according to the number of references to the variable defined in the node. Additionally, each flow graph has two special nodes representing its entry and its exit node. 1 Note that a switch-statement in C or Java can be transformed into a nested if-then-else-construct.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

#include

42 43 44 45 46 47 48 49 50 51 enum optionstyle { american, european, ladder }; 52 enum optiontype { call, put }; 53 54 class option { 55 private: 56 double* strike; 57 double locked_profit; 58 double maturity; 59 optionstyle style; 60 optiontype type; 61 underlying* s; 62 public: 63 option(optionstyle style_, optiontype type_, 64 double strike_, double mat_, underlying* s_); 65 option(optionstyle style_, optiontype type_, 66 double* strike_, double mat_, underlying* s_); 67 double pay_off(double t); 68 int update_strike(void); 69 }; 70 71 option::option(optionstyle style_, optiontype type_, 72 double strike_, double mat, underlying* s_) { 73 style = style_; 74 type = type_; 75 strike = new double; 76 *strike = strike_; 77 maturity = mat; 78 s = s_; 79 locked_profit = 0.0; 80 } 81 82 83 84

class underlying { private: double pr; public: double price(void) { return pr; } double changepr(double dpr) { return pr += dpr; } };

option::option(optionstyle style_, optiontype type_, double* strike_, double mat, underlying* s_) { style = style_; type = type_; strike = strike_; maturity = mat; s = s_; locked_profit = 0.0; } double option::pay_off(double t) { int exerciseable; double res; exerciseable = style == american || style == european && t == maturity || style == ladder && t == maturity; if (!exerciseable) return 0.0; if (type == call && s->price() > *strike || type == put && s->price() < *strike) { res = fabs(s->price() - *strike) + locked_profit; return res; } if (s->price() == *strike) return locked_profit; if (type == call && s->price() < *strike || type == put && s->price() > *strike) return locked_profit; } int option::update_strike(void) { if (style != ladder) return -1; if (type == call && s->price() >= *strike || type == put && s->price() = s->price()) { locked_profit += fabs(*(strike+1) - *strike); strike++; } } return 1; }

Figure 2: C++ source code of class option Each individual flow graph is connected to an abstract test driver which invokes the appropriate constructor of the class, sets an initial state and invokes the methods of the class in a particular sequence. After the invocation of the methods in some order, the current state of the object is checked to test whether the methods interact with each other as specified and the destructor of the class is invoked. Setting an initial state and verifying the current state after a method sequence are represented in a CCDFG by nodes labeled set initial state and verify state, respectively. The flow graphs of the methods are connected to nodes of the CCDFG labeled select constructor, select next method and end of sequence. These nodes represent predicate statements which schedule methods in some order. Additionally, these flows graphs may also be interlinked due to mutual dependencies concerning control and especially data flow, as it will be shown below. Figure 3 shows the CCDFG of class option generated on the basis of the specification in figure 1 and the implementation in figure 2. In this figure, rectangles represent single statements, while filled circles represent the entry and the exit node of individual methods. Numbering the rectangles was done in correspondence to line numbers in the source code. These rectangles are connected to each other through different types of edges. Solid edges represent dependencies with respect to control flow, whereas dashed edges represent those with respect to data flow. The thickness of each edge indicates, whether it visualizes an intra- or an inter-method dependency.

3.5

CCDFG Generation

Two steps can be distinguished during the construction of a CCDFG. First, a graph corresponding to the CCFG of the class is constructed. Second, this graph is augmented with data-flow information obtained from the specification. Since CCFGs and their construction is described in [6, 10] in detail, we do not explain the first step. In the second step, two kinds of data flow edges are distinguished: 1. The first group contains edges indicating intra-method data flow. These edges can be determined with respect to the source code of the class. 2. The second group contains edges indicating intermethod data flow using the CFG of the class. Analyzing the specification of the class in figure 1, we can determine that the action parts of following transitions contain a definition of attribute strike: t1 , t2 , t3 , t4 , t5 , t6 , t8 , t11 and t14 . Uses of this attribute can be found in the predicates defining the states in, at, out of the class, in the action parts of transitions t8 , t9 , t11 , t14 and in the guard parts of transitions g7 , g8 , g10 , g11 , g13 , g14 , g16 , g17 , g18 . After the identification, these definitions and uses are associated to each other according to a data flow criterion. For instance, the all-definitions criterion [9], which requires testing of each definition of an attribute at least once through a use, can be fulfilled through test cases covering the following def-use pairs: (t1 , t9 ), (t2 , t9 ), (t3 , at), (t4 , at), (t5 , out), (t6 , out), (t8 , at), (t11 , t14 ) and (t14 , out). Next, these def-use pairs have to be represented within the CCDFG of the class through special edges visualizing inter-method data flow. This requires the identification of

select constructor option()

option() 33

44

34

45

35

46

36

47

37

48

38

49

39

set initial state select next method update_strike()

pay_off() 55 t 59

t 74

58 f t 62 63

60

f

t t 79

65 f t 66 t 67

f 75 77

f

f

80

69

f

73

83

end of sequence t verify state ~option()

Figure 3: Class control/data flow graph of class option the definition and the use of each def-use pair within the implementation of the class. Note, that so far the CCDFG of the class only contains information gathered using its implementation, whereas the definitions and uses of the attributes are identified and associated to each other using the specification of the class. So, this is exactly the point where the integration actually takes place. The definition of an attribute is usually implemented in the source code as an assignment statement which has the form ‘< attribute >= . . .’. For instance, the definition of the attribute strike in node t1 is implemented in line 36 of the source code as ‘*strike=strike ’. Unfortunately, the identification of uses is not simple like that. Especially the identification of predicates characterizing states can be quite difficult, because the notion of a state only appears in the specification and not in the source code of the class. However, as it was mentioned before, objects have a state dependent behavior. Thus, it should be possible to locate a predicate in the source code of each method which selects a block of statements to be executed dependent on the values of some attributes. Such predicates should exist for each state defined in the specification. Otherwise, the class is not correctly implemented. For instance, only an option which is

1: procedure SelectTests(G, G0 , T ) 2: input G : Node in the CCDFG of initial prototype 3: G0 : Node in the CCDFG of the modified prototype 4: T : Test cases used for testing the initial prototype 5: output T 0 : Test cases selected for testing the modified prototype 6: begin 7: f urther traversal = true 8: mark G and G0 as visited 9: compare G and G0 10: if G0 is modified or affected then 11: /* test cases for white box testing */ 12: if G0 respresents a simple assignment then 13: for all intra-method data dependent successor N 0 of G0 do 14: add all test cases covering G to N 0 .aff ected 15: else 16: add all test cases covering G to T 0 17: f urther traversal = f alse 18: if G0 is affected by a changed definition in another node then 19: add to T 0 all test cases covering G0 and the changed definition 20: 21: /* test cases for black box testing */ 22: for all inter-method data dependent predecessor N 0 of G0 do 23: add all test cases (t1 , t2 ) to T 0 , with t1 covering N 0 and t2 G0 24: for all inter-method data dependent successor N 0 of G0 do 25: add all test cases (t1 , t2 ) to T 0 , with t1 covering G0 and t2 N 0 26: if f urther traversal then 27: associate control dependent successors of G to successors of G0 28: for all unassociateable successor N of G do 29: add all test cases covering N to T 0 30: for all associateable successor N of G do 31: T 0 = T 0 ∪ SelectTests(N, N 0 ) 32: return T 0 33: end

Figure 4: Test case selection algorithm of Rothermel et al. adjusted for CCDFGs

in-the-money has a pay-off greater than zero, this means the result of the method pay off() has to be greater than zero if the option is in-the-money and zero otherwise. Therefore, there has to be a predicate distinguishing in-the-money options from others. Such a predicate can be found in line 60. Problems can arise, when several predicates are combined or when predicates are omitted to ease the implementation. Often, the behavior of a method is similar or equal for some states like in the case of the method pay off(). The method pay off() has a similar behavior for at-themoney and out-of-the-money options. This common behavior can be used to improve the implementation by handling both cases simultaneously. This means instead of the two predicates ‘s.price = strike’ and ‘s.price < strike’, both cases can be handled by the predicate ‘s.price ≤ strike’. In these cases, the identification of predicates would not be possible. There are two solutions for this problem. First, predicates may not be combined or omitted, although this would lead to an improvement of the implementation. Second, to use a data flow criterion like all-definitions which can be fulfilled with only a subset of the uses, this means a data flow criterion which does not imply the identification of all uses. Similar to definitions, uses in guards or actions can usually be identified without any difficulties.

3.6

An Algorithm for Test Case Selection

The CCDFG of a class can be used in various ways for testing. Generally, a CCDFG can be used to generate test cases fulfilling some coverage criteria including the above identified def-use pairs. Following the approach of Rothermel et al. for CCFGs, CCDFGs can also be used to verify

changes between two versions of a program by comparing their CCDFGs. An algorithm implementing the latter strategy for testing classes within object-oriented prototypes is given in figure 4. The algorithm in figure 4 takes as input the root nodes of the CCDFGs of the two prototype versions and a set of test cases used for testing the former version of the prototype. Based on this input, it selects test cases for safe testing of the latter version. The selected test cases can be distinguished in two groups. The first group contains test cases required for testing changes in the implementation. Each of these test cases consists of an input for the constructor of the class, if one is required, and an input for the method where a change is found. The second group contains test cases required for verifying modifications in the specification. These test cases consist of an input for the constructor, an input for the method which defines an attribute and an input for the method which uses the attribute. To ease the notation, test cases for white-box testing will be represented by a single input, whereas test cases for black-box testing will be represented by a pair of test inputs, assuming the constructor requires no input. In the following description of the algorithm, nodes in a CCDFG will be referred by upper-case letters, nodes in the latter version will be marked by an apostrophe. Starting with two nodes G and G0 , the algorithm marks as a first step both as visited to avoid multiple traversal of the same nodes (line 8). Next, it compares both nodes. A modification of the statement represented by G0 is handled according to the type of statement represented by this node (line 12). As explained above, a modification in a simple assignment is handled by adding all test cases covering this assignment to a special test case set called aff ected of the data dependent successors (lines 13–14). The aff ected-set of a statement contains test cases which execute a modified definition of the variable used within this statement. A modification of a statement which is not a simple assignment is handled by adding all test cases covering this statement to T 0 (line 16). In this case, there is no further traversal of the succeeding nodes required, because all test cases which can reach these nodes are already selected (line 17). After that, if any test cases were added to the aff ected-set of G0 , i.e. a variable with a modified definition is used within the statement represented by G0 , all test cases which cover both the changed definition and G0 are added to T 0 (lines 18–19). So far, only test cases for white-box testing were selected. Test cases for black-box testing are selected in the next four lines of the algorithm. For black-box testing, we have to test the def-use pairs obtained based on the specification. Obviously, errors can only be revealed by def-use pairs with either a modified definition or a modified use or both. Thus, only those test cases (t1 , t2 ) are added to T 0 which either execute a definition and a modified use in G0 or a modified definition in G0 and a use in another node belonging to another method.

4

CONCLUSIONS We have presented a technique for testing classes in

object-oriented prototypes. This technique operates on a graphical representation of the class called class control/data flow graph (CCDFG). A CCDFG constitutes of rectangles representing statements of the methods and arrows representing control and data flow. These arrows are generated on the basis of both the source and the specification. This means, a CCDFG visualizes information from source code and the specification at the same time. Changes to the implementation and the specification during a prototyping iteration can be identified through comparing the CCDFGs of the two versions of the prototype. After the identification of these changes, test cases can be identified covering them. It is important to mention another possible application of our method. The proposed method can also be used for class-level regression testing. Since testing of prototypes is interpreted as a regression testing problem, there are no prototyping specific constraints imposed on the testing method.

REFERENCES [1] Y. H. Chen, T. H. Tse, F. T. Chan, and T. Y. Chen. In black and white: an integrated approach to class-level testing of objectoriented programs. ACM Transactions on Software Engineering and Methodology, 7(3):250–295, July 1998. [2] J. Connell and L. Shafer. Structured Rapid Prototyping. Prentice Hall, Englewood Cliffs, 1989. [3] J. Connell and L. Shafer. Object-Oriented Rapid Prototyping. Prentice Hall, Englewood Cliffs, 1995. [4] M. E. Fagan. Design and code inspections to reduce errors in program development. IBM Systems Journal, 15(3):182–211, 1976. [5] C. Floyd. A systematic look at prototyping. In R. Budde, K. Kuhlenkamp, L. Mathiassen, and H. Z¨ullighoven, editors, Approaches to Prototyping, pages 1–18. Springer-Verlag, Berlin, 1984. [6] M. J. Harrold and G. Rothermel. Performing dataflow testing on classes. In Proceedings of the Second ACM SIGSOFT Symposium on the Foundations of Software Engineering (New Orleans, Louisiana, December 6–9), volume 19 of ACM SIGSOFT Software Engineering Notes, pages 154–163, 1994. [7] H. S. Hong, Y. R. Kwon, and S. D. Cha. Testing of objectoriented programs based on finite state machines. In Proceedings of the Second Asia-Pacific Software Engineering Conference (Brisbane, Australia, December 6–9), pages 234–241, 1995. [8] J. Hull. Options, Futures, and other Derivatives. Prentice Hall, Upper Saddle River, 3 edition, 1997. [9] S. Rapps and E. J. Weyuker. Selecting software test data using data flow information. IEEE Transactions on Software Engineering, 11(4):367–375, Apr. 1985. [10] G. Rothermel, M. J. Harrold, and J. Dedhia. Regression test selection for C++ software. Software Testing, Verification & Reliability, 10(2):77–109, June 2000. [11] W. W. Royce. Managing the development of large software systems: Concepts and techniques. In Western Electronic Show and Convention (Los Angeles, CA, August 25–28), volume 14 of WESCON technical papers, pages 1–9, Los Angeles, 1970. WESCON.