Design Recovery for Software Testing of Object ... - Semantic Scholar

3 downloads 12538 Views 242KB Size Report
Nov 5, 1992 - Design Recovery for Software Testing of. Object-Oriented ... data de nition and use paths across functions and objects. Objects have states and ...
To appear in Proceedings of Working Conference on Reverse Engineering, Maryland, May 21 { 23, 1993

Design Recovery for Software Testing of Object-Oriented Programs C.H. Kung, J. Gao, P. Hsia Computer Science Engineering The Univ. of Texas at Arlington P. O. Box 19015 Arlington, TX 76019-0015 Fax: (817) 273-2548, Email: [email protected] J. Lin and Y. Toyoshima Fujitsu Network Transmission Systems, Inc. 3099 North First Street San Jose, CA 95134-2022 November 5, 1992 Abstract: Object-oriented features such as encapsulation, inheritance, and state dependent behavior raise new problems in software testing. In this paper, we describe a reverse engineering approach for software testing of object-oriented programs. The approach is based on a graphic model which consists of three types of diagrams: object relation diagram, block branch diagram, and object state diagram. An object relation diagram presents the inheritance, aggregation, and association relations among the object classes. A block branch diagram presents, for a member function, the control structure, the input parameters and output parameters, the external data that are used and a ected, and other functions that are invoked. An object state diagram presents the states and state transitions. Rules for generating these diagrams from a C++ program will be described. These diagrams may be used to 1) provide guidance on the order to test the classes and member functions; 2) prepare member function test cases; 3) prepare test cases for object state dependent hehaviors and interaction between such behaviors; and 4) provide graphic display of coverage information to a tester. Key Words and Phrases: Software testing, reverse engineering, object-oriented programming, functional testing, structural testing, test strategy, test tool.

1 Introduction Software testing is an important software quality assurance activity. The objective of software testing is to uncover as many errors as possible with a minimum amount of time and e ort. During the last two decades, many software testing methods and techniques have been proposed. However, most of these were based on the traditional function-oriented paradigm. They have been found to be inadequate for OO systems [harr91a] [wild91a] [perr90a] [smit90a]. The problem lies in the fundamental and conceptual di erences in terms of functions, and objects. Features such 1

as encapsulation, object state dependent behavior, object interaction, inheritance, polymorphism, and dynamic binding provide visible bene ts in software design and implementation. However, these new features also raise challenging problems for software testing and maintenance. Encapsulation and information hiding result in chains of member function invocations that often involve objects of more than one class1 . Potential problems for software testing are: 1) it is dicult to understand the code and prepare test cases; 2) it is not cost-e ective to construct test stubs for member functions since most of them consist of one to two statements [wild91a]. Rather, one would just use them provided that they have been (adequately) tested2 ; 3) it is necessary to determine and limit the required regression tests when a function or a class is changed; 4) it requires a fresh look into the traditional coverage criteria and to extend them to include not just coverage of individual functions, but also function invocation sequences, object states and state sequences, and data de nition and use paths across functions and objects. Objects have states and state dependent behaviors. Hence, object-oriented programming is di erent from traditional function-oriented programming. Problems relating to software testing of state dependent behavior include: 1) how to characterize object states; 2) how to identify object states from a program (not from a speci cation); 3) how to identify state transitions; 4) what types of state machines are appropriate; 5) how to de ne the state machines for higher level objects (such as aggregate objects and objects that are aggregates of an array of objects), and derived objects; 6) how to test interactions among objects in terms of their state dependent behavior; and 7) what are the coverage criteria for state dependent behavior. In this paper, we present a reverse engineering approach to software testing of OO programs using a graphic model, called Object-Oriented Test Model (OOTM). The design of the model has taken into consideration problems that a tester may face when testing an OO program. The model has a mathematical basis but the intention of this paper is to present only the graphic representation (not its mathematical de nition, which will be described in another paper). The model is a reverse engineering model consisting of three types of diagrams: 1) an object relation diagram that describes the inheritance, aggregation, and association relations; 2) a set of block branch diagrams that describe the control structures of the functions and control relations between the functions; and 3) a set of object state diagrams that describe the state dependent behaviors of the objects. The tool supports functional unit testing and structural unit testing, intraclass integration testing, interclass integration testing, and testing of state dependent behavior of a single class as well as interaction of several classes. The layout of this paper is as follows. Section 2 presents an overview of the software architecture and the graphic model, assumptions and limitations of the current version. Section 3 provides a detailed description and how the model can be used to facilitate test case generation. In section 4 we present conclusions and future work.

2 Overview The objectives of developing the model are: This paper is written with C++ in mind. However, the results are not limited to C++. Stubs may be needed for chains of function invocations if the chains are frequently executed and the execution time is considerably long. 1

2

2

1. to help testers understand the structures of, and relations between the components of an OO program; 2. to provide testers a systematic method and guidance to perform OO testing; 3. to assist testers to nd better test strategies to reduce test e ort; 4. to facilitate testers to prepare test cases and test scenarios, and generate test data; and 5. to aid testers set up test harnesses to test a speci c test component. The new features introduced by OO programming require a new set of software testing capabilities. The OOTM model recognizes this and is designed to o er the following supports:

 it displays an OO program at di erent levels: function, object, subsystems, and system levels.    

Hierarchical representation of these components is supported; it displays states and behaviors of the object classes; it displays interfaces of di erent components; it displays interfaces between the object classes; and it displays the environments of the functions.

OOTM consists of three types of diagrams:

 An object relation diagram, which presents the inheritance, aggregation, and association re-

lationships among the object classes. Information contained in this diagram may be used to provide guidance on the order to test the classes, to indicate which classes are a ected in regression testing, and to display class coverage information.  A set of block branch diagrams, which present the control structures of the functions and member functions. Also presented are the interfaces of these functions so that a tester would know which data are used and/or a ected by each function. Information contained in these diagrams may be used to prepare functional test cases, structural test cases, and test harnesses in member function unit testing, to derive data dependence relations across multiple functions, and objects, and to display statement, branch, and path coverage information.  A set of object state diagrams, each of which presents the state dependent behavior of an object class. Information contained in these diagrams may be used to prepare test cases for testing state dependent behaviors of the objects and interaction among their behaviors, and to display state coverage, transition coverage, and transition path coverage information.

An overview of the software architecture that support the model is shown in Figure 1. The environment consists of a Motif based graphic user interface and X Window based graphic display, a parser, a displayer, and a collection of utilities. Although at the present the project is for software testing of OO programs, the environment has been designed to include also re-structuring and re-engineering in the future. 3

Figure 1: The underlying software architecture The parser is implemented using lex and yacc. The main function of the parser is to analyze the C++ source les and extract the various relationships, control ow, data/object ow, and other information useful for software testing. This information is stored in various data structures in the internal representation data base. A tester can instruct the system to parse the source les using several options, including: 1) parse all the les in a given path, which speci es the directories containing the les; 2) parse a particular le; 3) parse a particular function. A tester can instruct the system to display the various diagrams described above and examine the various parts of the application program, or object class library. This reverse engineering environment facilitates a tester to understand the structure and internal logic of the application program, or library. The collection of utilities provides various capabilities useful in the testing process. For example, it allows a tester to quickly and automatically generate test orders, test plans, test cases, and test data. A tester may request the system to suggest a test order for testing the classes in an application program so that the number of stubs needed is mininal. Another example is to request the system to automatically generate test data for testing some or all of the basis paths in a member function. The utilities also can animate a testing process, displaying which of the classes and functions have been tested, allowing a tester to quickly identify the untested components, and querying the values of selected variables. The work presented in this paper is based on the following reasonable assumptions: 1) that the program is free from syntactic errors; and 2) that a requirements speci cation exists either physically or in someone's mind but the speci cation may be incomplete. The current work does not consider templates and nested classes. These issues will be reported in future publications.

4

3 Object Relation Diagram A object relation diagram (ORD) displays various relations among the object classes of an objectoriented program. In particular, an ORD for an object-oriented program is a multigraph ORD = (V; E ), where V is the set of object classes and E  V  V is the set of relationships between the classes. The relationships are classi ed into: inheritance relationships, aggregation relationships, and association relationships. In the following discussion, we use Class A, Class B, Class C, etc. to denote the names of object classes. The relationships between object classes are identi ed as follows:

 Inheritance Relationships. These relationships are identi ed according to declarations of the following forms, these declarations are usually found in the header les: class Class D: Class B class Class D: public Class B class Class D: protected Class B class Class D: private Class B

 Aggregation Relationships. These relationships specify that an object class is part of another object class. The class that contains the other class(es) is called the aggregate class. There are three types of aggregations:

{ Automatic aggregations are identi ed according to declarations of the following forms: class Class A f g;

Class B b; // an instance of Class B is part of Class A Class C c[m]; // an array of instances of Class C is part of Class A // ...

{ Static aggregations are identi ed according to declarations of the following forms: class Class A f g;

static Class B b; // a static instance of Class B is part of Class A static Class C c[m]; // a static array of instances of Class C is part of Class A // ...

{ Dynamic aggregations are identi ed according to declarations of the following forms: class Class A f

Class B *b; // a dynamic instance of Class B is part of Class A static Class C *c[m]; // a dynamic array of instances of Class C is part of Class A // ... Class A (); // constructor for Class A g; Class A::Class A () f // dynamically creating b; // dynamically creating c[m];

g

Note dynamic aggregation requires the aggregate class dynamically creates (i.e., uses the new operator) its member class instances in the free store. If it is not the case, then the relationship is classi ed as an association relationship. 5

 Association Relationships. There are four types of association relationships: { Friend member function associations. These are identi ed according to declarations of

the following forms: class Class A f // ... friend return type Class B::f (...); // ... g; { Friend class associations. These are identi ed according to declarations of the following forms: class Class A f // ... friend class Class B; // ... g; { Friend operation associations. These are associations between classes through a global function. For example, class date; class time f // ... friend char *time date (time, date); // ... g; class date f // ... friend char *time date (time, date); // ... g; char *time date (time t, date d) f ... g; { Ordinary associations. These are associations that are established through parameter passing of an instance of one class to a member function of another class.

The graphic notations for these relationships are depicted in Figure 2. Symbol =) u

-

Relation inheritance aggregation association

Annotation public protected private " auto static dynamic " friend[ function class operation] par passing " j

j

j

j

j

j

j

j

j

j

Figure 2: Graphic notations for object relations

3.1 An Example Figure 3 shows an ORD for the elevator example. It is explained through the following paragraphs: 6

 The declaration \class building: public action" is represented in the gure by the inheritance

edge from building to action.  The following declaration is represented by the aggregation edges that connect building to persCollection, oorCollection, and elevCollection. Moreoverrrrr, the aggregation relations are one-to-one relations, meaning that each building instance has exactly one persCollection,

oorCollection, and elevCollection instance. class building : public action f private: persCollection thePersons; // People in the system

oorCollection theFloors; // Floors in the building elevCollection theElevators; // Elevators in the building public: // ... g;

 From the following elevator class declaration, three association relationships are identi ed, i.e., setDirection, elevStopping, and action, as shown in Figure 3. These associations re ect the fact that instances of oorCollection and persCollection have been passed as parameters to the member functions of the elevator class: class elevator f // ... public: elevator(); int getPassengers(void) f return passengers; g void setelevNumber(int n); void showElevator(void); void setDirection( oorCollection &theFloors); int elevStopping( oorCollection &theFloors); void action( oorCollection &theFloors, persCollection &thePersons); g;

3.2 Using the Object Relation Diagram As stated earlier, an ORD may be used to provide guidance on the order to test the classes. Motivation to provide such guidance comes from the following observations:

 In OO programming, many member functions consist of only one to two lines of code [wild91a].

It does not pay to construct a stub for such a function; one would rather use the function directly provided that the function has been tested. This implies that a preferred sequence to test the object classes and the member functions of a class is needed.  It is not always as easy as in conventional software testing to construct stubs and drivers. The diculty comes from understanding of a chain of function invocations and constructing 7

Figure 3: An object relation diagram for the elevator example

8

stubs or drivers that must access the private parts of more than one objects. If the classes are tested in some order, then the need to construct stubs and drivers for untested functions can be eliminated3.  In OO testing, a tester often needs to answer questions like \to test this class, what other classes must have been tested?" The ORD can be used to answer such questions. An ORD describes the inheritance, aggregation, and association relations among the object classes. An ORD may consist of several directed subgraphs, which are mutually disconnected. It is easy to identify such connected subgraphs. These connected subgraphs can be tested in parallel. Therefore, test order is needed only for classes of each connected subgraph. The discussion below has this assumption in mind. In some cases, a connected subgraph does not contain directed cycles, i.e., cycles that consist of directed edges of the same direction. In such cases, a test order can be generated by using topological sorting [ahoh83a]. For example, the elevator ORD shown in Figure 3 can be used to derive the following testing order for the classes: 1) person; 2) persCollection; 3) oor; 4) oorCollection; 5) elevator; 6) elevCollection; 7) building; 8) main, and action can be tested any time before building. In many practical applications, directed cycles exist for a collection of classes. Consider, for example4 , an application containing three classes: employee, manager, and department. Manager is a derived class of employee, employee has an association with department (so that one can easily nd out the department information for an employee), and department contains a data member which is a manager (i.e., aggregation). It is likely that some member functions of the manager class would use some inherited functions from the employee class, the employee class would use some member functions of the department class to get information about departments, and department would use some member functions of manager to update manager information for a department. Thus, we have a directed cycle consisting of employee to department by association, department to manager by aggregation, and manager to employee by inheritance. Our research indicates that such cyclic relationships exist in many real world application programs, e.g., in the InterViews library several collections of classes manifest such cycles, as depicted in Figure 4 The reverse engineering approach described in this paper facilitates the solution of the test order problem for classes that has a cyclic ORD. We have proved5 that any directed cycle in an ORD must contain at least one association edge. Unlike classes that are related through inheritance or aggregation, classes that are related through an association relationship do not share common data. Therefore, eliminating an association edge makes it possible to test the member functions of the source class of the association edge that do not invoke functions of the destination class of the association edge. The other classes of the cycle then can be tested similarly. After all the classes of the cycle are so tested, the remaining member functions of the classes can be tested. If none of the member functions is a recursive function, then this approach e ectively eliminates the need of constructing the test stubs. 3 To reduce the time needed to execute a chain of tested functions, stubs may be constructed that simply return the results corresponding to given inputs. This may be achieved by executing the chain of tested functions with the given inputs and recording the corresponding results. 4 We have worked out the example but it is too tedious to include in this paper. 5 It is beyond the scope of this paper to present the proof, which will be published elsewhere.

9

Figure 4: Inheritance, aggregation, and association cycles in InterViews

10

De

P

B Fe De0

Figure 5: The components of a BBD

4 Block Branch Diagram A block branch diagram BBD for a C++ function C::f is a 5-tuple C::BBD = (De ; De ; P; Fe ; B ), where C denotes a class and it may be absent (in this case f is a globally de ned function), and 0

  

= fdi jdi is a data item used by f and declared outside the scope of f g; De = fdi jdi is a data item changed by f and declared outside the scope of f g; P = x1 1 ; x2 2 ;    ; xn n ; xn+1 n+1 is the list of parameters and return result of f , where each i; 1  i  n, is either "; #, or l, denoting that the parameter xi is an input, output, or input and output parameter; if xn+1 n+1 is absent, then the return of f is void, otherwise, xn+1 = f and n+1 =";  Fe = ffijfi is a function invoked by f g; and  B is a directed graph, called the block body, to be de ned later, that represents the control structure of f . De 0

0

0

These components of a BBD are graphically represented as in Figure 5. The block body B is a directed graph B = (V; E ) that represents the control structure of f and satis es the following conditions: 1. there is exactly one vertex, called the starting vertex, denoted vs , with indegree zero: jfvjv 2 V ^ indegree(v) = 0gj = 1 2. there is exactly one vertex, called the nal vertex, denoted vf , with outdegree zero: jfvjv 2 V ^ outdegree(v) = 0gj = 1 3. all the other vertices have indegree one and outdegree either one or two; each of these vertices also satis es the following conditions:

 if it has outdegree one, then it represents either a function call or a sequence of simple statements;

11



START



  start vertex   END

sequentail statements

sequential vertex function invocation

"bbb " " simple "bbcondition "b bb"""

simple decision vertex

  function call vertex nal vertex

-

control ow

Figure 6: Graphic symbols for BBD vertices /* { Have person enter elevator if waiting for an elevator on this

oor regardless of direction. (Elevator is empty and the person will determine its direction.) Return the person's destination in pdest to simulate that person pressing one of the elevator's oor buttons. */ int person::loadIfWaiting(int elevNumber, int oorNumber, int &pdest)

f

if (waitingForElev && ( oorNowOn == oorNumber)) f waitingForElev = 0; // Not waiting any longer elevNowIn = elevNumber; // Save elevator number pdest = destination; // Pass person's destination back return TRUE; // Person got on board

g

g

return FALSE; // Person did not get on board Figure 7: Source code for person::loadIfWaiting (...)

 if it has outdegree two, then it represents a decision vertex for a simple condition; 4. Every vertex of B occurs on some path vs {vf . The graphic symbols for representing a BBD is depicted in Figure 6. Note that in the de nition of a BBD, the decision vertices represent only simple conditions, that is, conditions that do not contain a binary logical operator &&, or jj. Using these diagrams, test cases generated for basis path testing automatically cover branch relational operator testing [taik89a].

4.1 An Example As an example, we consider the member function person::loadIfWaiting(...) shown in Figure 7. This function simulates a person's behavior of getting on an elevator when the elevator stops by the

oor the person is on. In particular, if the person is waiting for an elevator, then he/she gets on the elevator and informs the outside world about this through the member function as an interface by 12

returning TRUE. Moreoverrrrr, it updates the person's status and passes the person's destination to the outside world. If the person is not waiting for an elevator or he/she is not on the same oor as speci ed by the parameter oorNumber, then FALSE is returned and nothing is changed. Figure 8 shows the BBD for the person::loadIfWaiting(...) member function and the meaning and usefulness of each part are explained as follows: 1. The upper left box shows the environment variables that are used by the member function. In particular, if a variable is pre xed by the class name and a dot, e.g., person.waitingForElev, then it is a data member of the class. Thus, the gure indicates that waitingForElev and destination are two data members of the person class. When preparing test cases for the member function, these environment variables must be set appropriately, depending on the type of testing (i.e., functional, or structural testings) to be performed. 2. The upper right box shows the parameters that are input into and output from the member function. In particular, elevNumber and oorNumber are input parameters, whereas pdest and loadIfWaiting are output parameters. When preparing test cases for the member function, the input parameters must be set appropriately and the output parameters must be observed to determined if the function works properly. 3. The lower right box shows the environment variables that are changed by the member function. In particular, if a variable is pre xed by the class name and a dot, then it is a data member of the class. The gure shows that waitingForElev and elevNowIn are two data members of the person class. These environment variables must be observed when testing a member function of a class. 4. To accommodate basis path, and branch testings as in some existing methods6, the diagrammatic representation of a BBD may also contain a dashed polygon. The polygon encloses the simple decision vertices that represent the compound condition originally appears in the function being displayed. In this way, the dashed polygon is used to represent the compound condition originally speci ed in the function and can be used to prepare test cases for basis path testing and branch testing in the traditional sense. In the Figure the dashed polygon enclosing the two diamond boxes represents the compound condition (waitingForElev && oorNowOn == oorNumber). This allows traditional basis path, or branch testings to be performed. However, we will show later that test cases prepared according to the traditional basis path testing and branch testing methods may not reveal logical operator errors. These errors are guaranteed to be detected if test cases are prepared to cover all the paths that are formed by the simple decision vertices, i.e., the diamond boxes. 6 For the sake of comparing software metrics, such as number of bugs per thousand source lines, with those established for previous project, for example.

13

Figure 8: BBD for person::loadIfWaiting

14

4.2 Using a Block Branch Diagram This section describes how to use a BBD to prepare test cases according to the type of testing required.

Structural Testing We illustrate test case preparation for the member function person::loadIfWaiting(...). The BBD for this member function has been shown in Figure 8. The test cases for basis path testing can be generated automatically and are speci ed in Figure 9. Explanations of these test cases are described below. The gure shows that the control structure for the member function has three closed regions, this means that there are three basis paths to be tested. These paths are listed in the Paths section of Figure 9. From the diagram, the input and output parameters and environment variables can be easily identi ed and are listed in the Input data, and Output data sections of Figure 9. For each of the three basis paths, a test case is speci ed. Each test case speci es 1. The constraints or conditions on the inputs. These constraints are identi ed from the simple conditions in the BBD and listed in the Constraints of path i section. When preparing the test data to exercise a path, the test data values must satisfy these constraints. 2. The given input data values. These are de ned to force the execution of a particular basis path and listed in the Given input data section. 3. The expected output values. These output values are speci ed in various ways, depending on the degree of knowledge of the tester about the application on hand and the quality of documentation. The ideal case is the tester has good knowledge about the application, as in cases in which the tester is the designer/programmer of the member function or the tester obtains such knowledge from reading the documentation. In this case, the tester speci es the expected output values according to the input data values. The worst case is that the tester has no knowledge about the application and the documentation is poor. In such cases, the expected output values are derived from the BBD. It must be pointed out that such values are not very useful because if the program itself is incorrect, then these values also are incorrect and cannot be used to detect program errors. The average case is that the tester has some knowledge about the application, and/or that the documentation provides some information about what the member function does. In such cases, the test tool will provide guidelines to help the tester in specifying the expected output values. The above cases will be further investigated in this project and the result will be reported in the future.

Functional Testing For each environment data item (i.e., data items in De ), called the preconditions of the member 15

Comment: Unit Test/Structure Testing/Basis Path Testing: Component: Person:loadIfWaiting(...) Path: Path 1: 0 - 1 - 2 - 3 - 5 Path 2: 0 - 1 - 4 - 5 Path 3: 0 - 1 - 2 - 4 - 5 Input data: Input P = f elevNumber, oorNowOn, oorNumberg De = f waitingForElev, elevNowIn g Output data: Output P = f pdest, loadIfWaiting g De' = f waitingForElev, elevNowIn g

Test case for Path 1:

Constraints of path 1: (1) WaitingForelev = 1 Given input data: elevNumber = 2

oorNumber = 1 destination = 2 waitingForElev= 1 Expectd Results: pdest = 2 waitingForElev= 0 elevNowIn = 2 loadIfWaiting = 1

(2) oorNowOn = oorNumber

oorNowOn = 1

Test case for Path 2:

Constraints of path 2: (1) WaitingForelev = 0 Given input data: elevNumber = 2

oorNumber = 1 destination = 2 waitingForElev= 0 Expectd Results: pdest = unde ned waitingForElev= 0 elevNowIn = 1 loadIfWaiting = 0

Test case for Path 3:

Constraints of path 3: (1) WaitingForelev = 1 Given input data: elevNumber = 2

oorNumber = 2 destination= 3 waitingForElev= 1 Expectd Results: pdest = unde ned waitingForElev= 1 elevNowIn = 1 loadIfWaiting = 0

oorNowOn = 1

(2) oorNowOn 6= oorNumber

oorNowOn = 1

Figure 9: Basis path test cases for person::loadIfWaiting(...)

16

function or method, the following values are speci ed, either by the tester or by a testing tool:

 Interested values, which are values that are typical for the data type, or values that used by

the member function to check the occurrence of some condition.  Boundary values, which are values that lie on the boundaries of the data type values, or lie just above or below an interested value.  Other values, which are values that a tester considers important.  Data, which are the union of the above values.

Test cases are generated by picking up exactly one element from each data value set and excluding the impossible combinations. For each test case, the tester speci es the expected results. As an example, functional test cases prepared for the person::loadIfWaiting member function is shown in Figure ??. The example is easy to understand so explanation is omitted.

5 Object State Diagram Object state dependent behavior is an important feature of OO systems. In OOTM, object state dependent behaviors are modeled by state transition machines, call object state diagrams (OSD). These diagrams are derived from the program code. We outline the main ideas and present brie y how to use the diagrams for preparing test cases to test the state dependent behaviors of the objects. Generally speaking, the OSD's are derived bottom-up. First, OSD's for base classes and component classes (i.e., classes that are part of other classes) are constructed rst. The construction of these OSD's in turn is done by constructing state transition diagrams for the individual data members. That is, for each data member of a class, a state transition diagram is constructed, which shows the states and state transitions for the individual data items. A state of a data item is a range of values which is derived from the decision vertices that evaluates the value of the data item and leads to updating of the data item. For instance, if x is an integer data member and x > 5 is a decision vertex, then, from this, two ranges of values, i.e., two states, for x can be derived: 1) [?1; 5); and 2) [5; 1], where \[" or \]" means including, whereas \(" or \)" means excluding the end point. Functions that change the individual data items are represented as state transitions for the data items. The individual data item state transition diagrams then are merged to yield an OSD for the object class7 . The identi cation and de nition of the states for the data items can be improved by domain analysis. This is because the values that a data item can take is a piece of domain speci c knowledge. Such domain knowledge can be used to provide more precise and accurate characterization of the data item states. The construction of an OSD for a derived class is outlined as follows. First a partial OSD for the class is constructed with respect to the data members that are speci c to the class. This partial OSD and the OSD for the base class then are merged to yield an OSD for the derived class. Again, the merge process is too complex and omitted here. 7

The merge process is a complex process and is omitted here.

17

Comment: Unit Test/Functional Testing/Equivalence Partitioning:

Component: Person::loadIfWaiting(...) Data items in preconditions of component: External input data in De: f waitingForElev, oorNowOn, destination g Input Parameters in P: f elevNumber, oorNumber g Data items in postconditions of the method: External output data in De': f waitingForElev, elevNowIng Output parameters in P: f loadIfWaiting, dpestg Analysis of input data of P: elevNumber: interested values: f0,1, MAXELEVSg boundary values: f32767, -32767g other values: f-1, -2, 2, MAXELEVS, MAXELEVS+1g

oorNumber: interested values: f0,1, MAXELEVSg boundary values: f32767, -32767g other values: f-1, -2, 2, MAXELEVS, MAXELEVS+1g Analysis of input data of De: waitingForElev: interested values: f0,1, -1g boundary values: f32767, -32767g other values: f-2, 2g

oorNowOn: interested values: f0,1, -1g boundary values: f32767, -32767g other values: f-2, 2g destination: boundary values: f32767, -32767g interested values: f0,1, -1, ENTER DESTg other values: f2, 5, MAXFLOORS, MAXFLOORS+1, -2 g

Functional test case 1:

Input data: waitForElev = 0

oorNowOn = 0 destination = 2

oorNumber = 0 elevNumber = 1 Expected data: pdest = unchanged elevNowIn = 1 loadIfWaiting = FALSE waitingForElev = 0

Functional test case 2:

Input data: waitForElev = 1

oorNowOn = 0 destination = 2

oorNumber = 0 elevNumber = 2 Expected data: pdest = 2 elevNowIn = 2 loadIfWaiting = TRUE waitingForElev = 0 Figure 10: Functional test cases for person::loandIfWaiting

18

av copies = 0 av copies > 0

recalls = 0 S1

S2

recalls < av copies recalls  av copies impossible S5 S3

S4

Figure 11: Identifying the states for an object

5.1 An Example In illustration, consider a library system in which book is de ned as follows8 : class book f // ... int av copies; // available copies rc item *rcl; // recall list int recalls; // number of recalls public: void check out ( ) f if (av copies > recalls) av copies??; else error (...);

g

void return ( ) f av copies++; if (av copies  recalls) send notice; g; void recall (char *patron) f // insert patron into *rcl and increment recalls; g; void check out rcb (char *patron); f // check out a recalled book g; // ...

g;

The av copies data item has two states: av copies = 0, and av copies > 0. The recalls data item has three states: recalls = 0, recalls  av copies, and recalls < av copies. The identi cation and de nition of these states may be done solely based on the program code, or by using domain knowledge. Merging these states and transitions gives rise to six combinations, i.e., by taking the Cartesian product of the states of the two data items (see Figure 11). One of these combinations is impossible, since av copies = 0 ^ recalls < av copies and the assumption that recalls  0 imply av copies = 0 ^ av copies > 0, which is impossible. Therefore, we have only ve states. After identifying the states, the member functions can be evaluated against the states and derive the state transitions. Figure 12 shows the OSD resulting from this analysis. The derivation of the state transitions can be done using two approaches: 1) program-based approach; and 2) requirements or knowledge-based approach. In program-based approach, we rst use symbolic execution [clar76a] to derive the e ects of the member functions. The e ects of a member function are expressed as rules associated with each path throughout the program. For example, the e ects of the check out() member function consists of two rules: av copies > recalls =) av copies = av copies ? 1 av copies  recalls =) error (:::) 0

8

This example has been considerably simpli ed to highlight the main ideas.

19

'$  S1 &%

t15

t16

? '$ t1 t2 S5  &%

'$ t12,t13 - S2  &%

t14

6

t3,t4,t5  ? '$



-

t11 t7

S4 &% t6

State speci cations:

S1: av copies = 0 and recalls = 0 S2: av copies > 0 and recalls = 0 S3: av copies > recalls > 0

'$ t8,t9,t10 S3  &%

S4: av copies > 0 and recalls > 0 S5: av copies = 0 and recalls > 0

Transition speci cations: t1: t2: t3: t4: t5: t6: t7: t8:

recall return/send notice [av copies < recalls] return/send notice check out rcb recall [av copies=recalls] return [av copies=recalls+1] check out [recalls>1] check out rcb

t9: return t10: [av copies > recalls+1] check out t11: [recalls=1] check out rcb t12: return t13: check out t14: [av copies=1]check out t15: return t16: recall

Figure 12: Reconstructed STD for library book object av copies0 denotes the new value of av copies resulting from the subtraction of 1 from av copies. The rules for each member function then are evaluated against each state to determine the resulting state. For example, applying the rules to S2 results in decrement of av copies and recalls unchanged (i.e., recalls = 0). Among the ve states, only S1 and S2 have recalls = 0, this

where

means that the application of the member function may result in S1 or S2. If the original values of av copies is 1, then the resulting state is S1, else, the resulting state is S2. Thus, av copies = 1 is used as a condition for the transition from S2 to S1. This analysis illustrates how transitions t13 and t14 are derived.

20

't2=r1 t1 ?  S1



$ 't4=r1  ?? S2

$ 

-

S3

  t5=r1  t3 6 6   t6=r 1 t6=r 1 Figure 13: State transition diagram for a hypothetic object

5.2 Using OSD's in Testing State Testing of Individual Classes The OSD can be used to prepare test cases as follows. First, the OSD is used to generate a test tree, which is a nite representation of the OSD. Second, from the test tree, we generate the test cases [kung84c] [kung85b]. We will see that the same approach can be used to generate test cases for testing interaction among object classes. A test tree is generated as follows: 1. Starting from the initial state S1 , the root of the test tree is constructed and labeled by S1 . 2. We now examine the nodes in the tree one by one. Let the node being examined be labeled by Si . If Si has already occurred at a higher level in the tree, then the node becomes a terminal node and will not be expanded. Otherwise, if there is a transition t leading from state Si to state Sj in the OSD, then we attach a branch and a successor node to Si . The branch is labeled t and the successor node is labeled Sj . The process must terminate since the number of states is nite. In illustration, a test tree for the OSD shown in Figure 13 is depicted in Figure 14. A test sequence is a sequence of operations leading from the root to a terminal or non-terminal node. For example, the following are some test sequences: t1-t2/r1-t3, t1-t2/r1-t6/r1, t1-t2/r1-t5/r1, t1-t2/r1-t5/r1-t4/r1 (we add t1 to put the OSD into the initial state). Test cases then are generated to force the program to execute the required test sequences. The execution results are analyzed to ensure that the program correctly implements the desired state dependent behavior of the class.

Testing Interaction between Classes In large, complex systems, objects interact with each other, resulting in concurrent, parallel computation threads. For example, a window object may trigger a gure object to draw the gure 21

t1 ?  S1

 t2=r 1 ?  S2

 HH t5=r1  t3  H   ?t6=r1 HH Hj  S3

S2

S1

   ?@ t6=r1 t4=r 1? ? @ @R  S2

S3

  Figure 14: A test tree in the window, and the gure object may further trigger rectangle draw, circle draw, and so forth. Test cases for such interacting objects are generated in a way similar to using Statecharts [hare88a]. Statecharts are visual formalisms that extend the traditional STD to include hierarchies and concurrency. Thus, an STD may be nested in another STD, and several STD's may execute concurrently. As described above, object behavior can be represented by an OSD. Therefore, the behavior of interacting objects can be represented by interacting OSD's. The interacting OSD's can be represented and analyzed by using Statecharts. In illustration, we show in Figure 15 a diagram representing the conjunction of three OSD's for three objects: A, D, H. Suppose A is in state B, D in state F, and H in state J, denoted by (B, F, J). Now if for some reason a transition from state J to state I occurs for object H, then the next system state will be (C, G, I) by virtue of e being generated by H and triggering the two transitions in A and D. Now if event n occurs, then object H will change state from I to J and trigger the f/g transition in A which in turn will trigger the g transition in D, resulting in (B, E, J). The execution of conjunctive OSD's can also be represented in a tree structure. The root of the tree is the initial state and the branches represent the alternative events. For example, in the initial state (B, F, J), three events, i.e., e, m, and k, can cause state changes. Thus, the initial state is expanded by three branches, labeled, respectively by e, m, and k. These events lead to three di erent states: (C, G, J), (C, G, I), and (B, E, J). These states are expanded in the same way, leading to more branches and states. A state is not expanded if it has occurred previously. This is because if a state has occurred previously, then any state that can be reached from the current state can also be reached from the previously occurred state; and hence, there is no need to expand the current state. Figure 16 shows part of the tree constructed, where states enclosed in rectangles are not to be expanded further since it has occurred earlier. Test sequences can be generated from 22

A ? ? D HYHkH B E ? 6 6 H ? e f/g n g ? ? F  e C G H

  m/e I  n/f

?  ? -J

Figure 15: Interactions between objects modeled in Statecharts the partial paths in the tree to test the interacting behavior of an OO system.

6 Conclusions In this paper, we have presented a reverse engineering model and its environment for software testing of OO programs. We have applied the approach to testing of the elevator sample program and found it useful in the testing process. We plan to apply the approach to the InterViews library. We found that the BBDs are useful in preparing test cases for structure as well as function testings. Unlike traditional programming, the source code of a member function does not explicitly specify which variables belong to which class; and hence, it is dicult to nd out such information without tool support. The BBD's make such information explicit and facilitate a tester in the testing process. In particular, a tester can initialize the environment variables in function testing and specify the expected results for the environment variables and parameters that are updated by the member function. We are currently in the process of implementing the environment. Besides the graphic display of the diagramming and interactive graphics based querying capabilities, the information captured by the model(s) will be used by various knowledge based tools to provide technical guidance/advise to a tester in the preparation of the test cases and in the generation of test data.

7 Acknowledgment Thanks are due to Fujitsu Network Transmission System, Inc. and Hewlett Packard Company for their support to the project. In particular, J. Burnham and R. Hyver of HP, and J. Zhu and R. Nassif of USWest provided valuable input to this research.

23

(B,F,J)

   

 HH   HH 

(C,G,J)

%e % g ee m f% % e % e (C,E,J) (B,E,J) (C,G,I) @ ? f n m ?? @@

%

e

m

k

HHH

(C,G,I)

%e % f % g ee n % e

(B,E,J)

e

(B,E,I) (C,E,I) (B,E,J) ?f ?@@n

@

?

HHH H

%e % e % n ee m % e % e (B,G,J) (C,E,I) (C,E,J) g m e ?@ ? ? @@

Figure 16: A partial tree presenting execution of concurrent objects

8 References [1] A. V. Aho, J. E. Hopcroft and J. D. Ullman, \Data Structures and Algorithms," Addison-

Wesley Publ. Comp., 1983. [2] L. Clarke, \A system to generate test data and symbolically execute programs," IEEE Transactions on Software Engineering, Vol. SE-2, no. 3., 1976. [3] D. Harel, \On visual formalisms," CACM, Vol. 31, No. 5, pp. 514 - 531, May 1988. [4] M. J. Harrold and J. D. McGregor, \Toward a testing methodology for object-oriented software systems," Department of Computer Science, Clemson University. [5] C. Kung, \A temporal framework for database speci cation and veri cation," Proc. 10th Intl' Conf. on VLDB, pp. 91 - 99, 1984. [6] C. Kung, \On veri cation of database temporal constraints," Proc. ACM SIGMOD Annual Conference on Management of Data, pp. 169 - 179, 1985. [7] D. E. Perry and G. E. Kaiser, \Adequate testing and object-oriented programming," Journal of Object-Oriented Programming, Vol. 2, pp. 13 - 19, January/February 1990. [8] M. D. Smith and D. J. Robson, \Object-oriented programming | the problems of validation," Proc. IEEE Conference on Software Maintenance | 1990. pp. 272 { 281. [9] K. C. Tai, \What to do beyond branch testing," ACM Software Eng. Notes, Vol. 14, No. 2, pp. 58 - 61, April 1989. [10] N. Wilde and R. Huitt, \Issues in the maintenance of object-oriented programs," University of West Florida and Bell Communications Research, 1991.

24

Suggest Documents