A Framework for Testing Object Oriented Software Using Formal Speci cations R. Fletcher and A. S. M. Sajeev Department of Software Development Monash University PO Box 197, Caul eld East Australia 3145 Email:
[email protected] [email protected]
Abstract
Object oriented methodology is increasingly being used for software development. There is now a need to develop testing methodologies for object oriented programs. These methodologies should exploit the characteristics of object orientation. In this paper we develop a framework for semi-automated generation of test cases for object oriented programs from formal speci cations. We use Object-Z as the formal speci cation language. We have designed and prototyped a test system called OZTEST . OZTEST understands characteristics of object-oriented specifciations including inheritance. It maps the speci cation to a corresponding implementation. Test shells generated from the given Object-Z speci cation are stored as test scripts. OZTEST chooses the test shells to be executed in a particular test-session based on user given criteria. A test class is created by inheriting from the class being tested. The child class thus has all the features of the class being tested, in addition to the routines to test the parent. The results of the test are stored in a le for veri cation.
Keywords: Software testing, Object-orientation, Inheritance, Formal speci cation
1
A Framework for Testing Object Oriented Software Using Formal Speci cations Abstract Object oriented methodology is increasingly being used for software development. There is now a need to develop testing methodologies for object oriented programs. These methodologies should exploit the characteristics of object orientation. In this paper we develop a framework for semi-automated generation of test cases for object oriented programs from formal speci cations. We use Object-Z as the formal speci cation language. We have designed and prototyped a test system called OZTEST . OZTEST understands characteristics of object-oriented specifciations including inheritance. It maps the speci cation to a corresponding implementation. Test shells generated from the given Object-Z speci cation are stored as test scripts. OZTEST chooses the test shells to be executed in a particular test-session based on user given criteria. A test class is created by inheriting from the class being tested. The child class thus has all the features of the class being tested, in addition to the routines to test the parent. The results of the test are stored in a le for veri cation. Keywords: Software testing, Object-orientation, Inheritance, Formal speci cation
1 Introduction Object orientation is a promising methodology of software development. It has provided several characteristics that have enhanced software development. Firstly, object-oriented development has focused on abstraction, allowing for the modeling of `real world' objects and their properties. The physical dimensions of the objects have been captured as attributes and rules of interaction as routines that may be performed on the attributes. Secondly, encapsulation has forced the user of such objects to only use the de ned routines to access the attributes (or state) of the object. No other access to the state is allowed, thus reducing the likelyhood of inappropriate usage of the object! Much work has been done in the areas of object oriented analysis, design and application (Booch 1991; Coad and Yourdon 1991). However, well de ned procedures do not exist for testing object oriented software. Even though it is theoretically possible to continue to use the traditional testing strategies, better testing methods can be developed by exploiting the features of object orientation. This paper provides a framework for testing object oriented software using formal speci cation. The use of formal speci cations in the software development process has many advantages. Firstly, the meaning of the speci cation is clear and can always be disambiguated by using the underlying mathematics. Secondly, unlike using a natural language speci cation for the system, there should be no misinterpretation because of the precision of the formal speci cation. Thirdly, we may use mathematical proofs to rigourously demonstrate aspects of the speci cation (Potter, Sinclair, and Till 1991). Formal speci cations have been used for test generation for non-object-oriented software (Carrington and Stocks 1994; Amla and Ammann 1992; Laycock 1992). We have designed and prototyped an object-oriented test-system called OZTEST . In the next section, we give an overview of OZTEST . Subsequent sections describe the dierent subsystems of OZTEST in detail.
2 Framework Overview In this section we will examine the Object-Z Testing framework (OZTEST ). The framework will allow the programmer to (semi)automatically generate test cases for an object-oriented programming language (refer to Figure 1). Test Configure Eiffel
Object Z
Test Configuration
Source
Specification
Test Configuration
Test Generation
Test
Test
Cases
Cases
System
Reference
Under Test
System Test
Test
Results
Results
Result Verifier Test Report
Figure 1: OZTEST framework The system is designed on the premise that the software to be tested is developed in an O-O language from an Object-Z speci cation. Object-Z is a model based object-oriented speci cation language (Duke, King, Rose, and Smith 1991). In Object-Z, a class is speci ed as a schema. A state schema and a number of operation schemas within a class schema specify the state and the methods of the class. Object-Z speci cation is declarational using the principles of set theory, predicate logic, relations and functions. An example of a schema speci cation is given in Appendix A. The OZTEST system uses Eiel (Meyer 1992) as that the implementation language. This does not mean that other object-oriented languages could not have been selected; we chose Eiel because, rstly, Eiel has cleaner semantics, secondly, it encourages formalism and thirdly it is used in both research and commercial environments. OZTEST consists of several subsystems:
The con guration system that allow the programmer to match up the classes, attributes and
routines of Eiel with the classes, state variables and operational schemas of Object-Z. The generator system that generates the test case shells. The actual system under test. The reference system to be used. The result veri er that produces a test report.
Throughout this paper we will be using a phone database example to demonstrate the framework. Both the Object-Z speci cation and the Eiel implementation of the phone database are listed in Appendix A.
3 Testing Con guration The test con guration system (Figure 2) allows the programmer to create the links between the formal speci cation in Object-Z and the the actual program in Eiel. Parent Configuration(s)
Flatten Configuration
Object-Z Class
Flattened Configuration
Flattened Eiffel Class
Match Up Configuration
Class Configuration
Figure 2: Test Con guration Subsystem For the system to generate and execute test cases, it needs to know what names the programmer has selected in Eiel for the corresponding Object-Z speci cation components. That is, the system needs to know for every class speci ed in Object-Z, what the name of the corresponding class in Eiel is. The system also needs to know what the corresponding state variables and operations are.
3.1 Con guration Flattening Both the speci cation and the implementation may have inherited classes. However, the inheritance hierarchy in both cases need not be the same. This problem is taken care of by attening the classes. The rst part of the con guration system is a program that generates attened con gurations. The process of attening out a con guration takes a few steps. This is performed by a program called fconfig. The steps taken are:
Class scanning
The (Object-Z) child (or class to be attened) is scanned (in LaTEXformat) and the class name, inherit statements, state variables and schema de nitions are stored in memory. Parent con guration loading For each inherited class (mentioned in the child) the corresponding con guration le is stored in memory. The system loads the parents in the order given in the child speci cation. The parent con guration les are expected to be in the le parentclass .cfg where parentclass is the name of the Object-Z class in lower case. Renaming Any renaming of state variables or schemas mentioned in the child are performed. This updates the parent information stored in memory.
Once the con guration has been attened, it is a simple matter of writing out the new attened con guration details. The le produced is called classname .cfg. The sections of the attened class are generated as follows:
Class name
This is simply the class name of the child class. Inherit section Again this is simply the child's inherit de nitions. The inherit de nitions of any parents (if any) are not rewritten. State variable section The state variables of the child and from every parents are appended together to form the
attened state variable list. Schemas section The schema section is slightly more complicated. { For any schema in the child that is not in a parent, a NEW schema de nition is made. { For any schema in the child that is in a parent, a REDEFINED schema de nition is made. { Finally, any schema that in only in the parent is copied as a RECURSIVE schema de nition. These terms have be taken from Turner and Robson (1993a). The next entry details if a schema is BEHAVIOURAL or NONBEHAVIOURAL (this will be described in Section 4.3) and lists any state variables that it may mutate. Finally there is a mention of the number of parameters that the schema accepts.
3.2 Implementation Mapping Once a attened con guration le has been generated by fconfig (see section 3.1 above). The next step is to match up the state variables and schemas speci ed in the Object-Z speci cation, with the attributes and routines in the corresponding Eiel implementation. At present the implementation mapping is performed manually. The prototype can be enhanced to automate the mapping provided the programmers accept certain uniform conventions in using identi ers in their programs for corresponding names in the speci cation. Once mapped the con guration le for the phone database example (given in Appendix A will contain the following: CLASS Phone = phone INHERIT STATE subscribers = subscribers telephones = telephones SCHEMA NewSubscriber = new_subscriber NEW NONBEHAVIOURAL subscribers; PARAMETERS 1 SCHEMA NewConnection = new_connection NEW NONBEHAVIOURAL telephones; PARAMETERS 2 SCHEMA IsSubscriber = is_subscriber NEW BEHAVIOURAL PARAMETERS 1 SCHEMA IsConnected = is_connected NEW
BEHAVIOURAL PARAMETERS 1 SCHEMA Disconnect = disconnect NEW NONBEHAVIOURAL telephones; PARAMETERS 1 SCHEMA RemoveSubscriber = remove_subscriber NEW NONBEHAVIOURAL subscribers; PARAMETERS 1 SCHEMA FindPhones = find_phones NEW BEHAVIOURAL PARAMETERS 1 SCHEMA FindSubscriber = find_subscriber NEW BEHAVIOURAL PARAMETERS 1 ENDCLASS
4 Test Generation The generation of the test data (Figure 3) will be based on the Object-Z speci cation. This means that the classes, variables and operations will be given with their Object-Z names. Object-Z Specification
Create Test Cases
New Test Shells
Raw Parent Test Shells
Test Configuration
Test Selector And Converter
Parent Converter
Converted Parent Test Shells
Eiffel Test Shells
Figure 3: Test Generation Subsystem
4.1 Test Case Generation We use an intermediate test speci cation language based on regular expressions. This intermediate speci cation language has been described in Fletcher (1994). The language is based on extending the work done by Kirani and Tsai (1994), where regular expression are used to describe the sequence of method calls that are allowed to be performed on a class. The advantage of using an intermediate language is that in future we can use other formal speci cation languages for speci cation. For the phone database example, we can specify the valid test sequences as:
SeqSpec (Phone ) ) Init SubscriberSchemas SubscriberSchemas ) ((NewSubscriber ConnectionSchemas RemoveSubscriber ) $ IsSubscriber ) ConnectionSchemas ) ((NewConnection (FindSubscriber ) Disconnected ) $ (IsConnected ; FindPhones ))
This states that a valid test sequence starts with the Init schema followed by SubscriberSchemas . Any SubscriberSchema starts with a NewSubscriber to create a new subscriber in the system. Once we have a subscriber we may then perform ConnectionSchemas ; these represent making telephone connection(s) for a subscriber. At any time during a SubscriberSchema you may issue an IsSubscriber to verify that a subscriber is in the system. Finally you may remove a subscriber using RemoveSubscriber . The ConnectionSchemas starts o with you establishing a connection for a subscriber (NewConnection ), then you may check for the subscriber of a connection (FindSubscriber ) before disconnecting it (Disconnected ). At any time during ConnectSchemas you may verify is a phone is connected (IsConnected ) and look up the phones a subscriber has (FindPhones ). From this intermediate speci cation we can generate valid test shells. We give some example of tests that may be generated: IsSubscriber NewSubscriber,IsSubscriber NewSubscriber,NewConnection,IsConnected NewSubscriber,NewConnection,FindSubscriber NewSubscriber,NewConnection,Disconnect,FindPhones ...
The framework requires that new test shells be stored in a le of the name classname .nts. Once test shells have been stored in the new test shell le they may then be used by the rest of the OZTEST system. Even though our example does not use inheritance (for the sake of brevity), the test system handles inherited classes and can regenerate test shells from those of the parent.
4.2 Parent Test Shell Regeneration If a class inherits another class, the test shells for that parent class may be inherited and regenerated for testing of the child. Test shells are regenerated by the pgen program (pgen stands for parent generation). To regenerate test shells from a parent, all schemas called must be converted to the names used in the child. Thus if there are any renamed schemas in the inheritance clause in the speci cation, the renaming must be re ected in the test shells. A class may inherit from more than one parent, so the test shells for each parent must be regenerated separately. During regeneration the test shells are assigned new test shell numbers. The test shells regenerated from parent classes are stored in a le called classname .pts. Because a class thus has two les containing test shells (classname .nts and classname .pts) the regeneration process for a parent included regeneration of tests from both test les.
4.3 Test Case Representation Test cases are stored as test shells. The test shells are simply test cases without the oracle to determine if the correct result has been obtained (in our framework the formal speci cation may take the place of the test oracle).
The test shell contains a sequence of operational schemas calls. The front of the sequence contains non-behavioural schemas (these represent operations that may modify the state) and the last schema is a behavioural (ie an operation that may not modify the state). In Object-Z, identi cation of behavioural and nonbehavioural schemas are straightforward. Non-behavioural schemas are identi ed by the presence of a delta () in its signature, and behavioural schemas by the absence of a delta.
4.3.1 Abstract Test Shells There are two type of test shell representations the rst is the formal (or abstract) representation and is used by the pgen program. The abstract test shell member contains the name of the schema to be called followed optionally by the list of parameters that will be passed to the schema. Each parameter is separated by angular brackets (< >). Between the angular brackets is the list of values in ascii format for that parameter. Each value is surrounded by a matching " " pair and is separated by white space. Some parameters may only have one value but with more complex parameters, a set, a sequence or a group of values may be present. For the phone database example we list a few of the abstract test shells that may be generated. BEGINTEST 120 SCHEMA NewSubscriber < "Rohan Fletcher" > SCHEMA NewConnection < "Rohan Fletcher" > < "031234567" > SCHEMA IsConnected < "031234567" > ENDTEST BEGINTEST 125 SCHEMA NewSubscriber < "Rohan Fletcher" > SCHEMA NewConnection < "Rohan Fletcher" > < "031234567" > SCHEMA FindSubscriber < "037654321" > ENDTEST BEGINTEST 127 SCHEMA NewSubscriber < "Rohan Fletcher" > SCHEMA NewConnection < "Rohan Fletcher" > < "031234567" > SCHEMA Disconnect < "031234567" > SCHEMA FindPhones < "Rohan Fletcher" > ENDTEST
4.3.2 Concrete Test Shells The second is the concrete (or implementation) representation and is generated by the tsac (described below) program and read in by the test harness. For the prototype, we assume that all test inputs are routine parameters. For instance, we don't consider inputs like les or mouse movement. Thus with the current version of oztest the only dierence between the two is that the concrete representation use Eiel names rather than Object-Z names.
Further research is needed to investigate other types of inputs. As a result, in a future version of the system a wider dierence between the two representation is proposed. For the phone database example the test shells mentioned earlier, after conversion, would look like the following: BEGINTEST 120 FEATURE new_subscriber < "Rohan Fletcher" > FEATURE new_connection < "Rohan Fletcher" > < "031234567" > FEATURE is_connected < "031234567" > ENDTEST BEGINTEST 125 FEATURE new_subscriber < "Rohan Fletcher" > FEATURE new_connection < "Rohan Fletcher" > < "031234567" > FEATURE find_subscriber < "037654321" > ENDTEST BEGINTEST 127 FEATURE new_subscriber < "Rohan Fletcher" > FEATURE new_connection < "Rohan Fletcher" > < "031234567" > FEATURE disconnect < "031234567" > FEATURE find_phones < "Rohan Fletcher" > ENDTEST
4.4 Test Selection Test selection is done by the tsac (Test Select And Convert) program. Before tsac may be run, you need to have run pgen and/or ngen to generate tests that may be selected from. The tsac program allow several types of test selection based on the following criteria:
Automatic Selection
This is based on the type of schemas in the child. Each test shell is checked to see what it is a unit test shell of. Then based on this it is decided if it will be selected. The selection methodology for automatic selection is as follows: { New Test Shell If a test shell is a unit test shell for a NEW schema, then it will only be present in the .nts le and is selection for execution. { Rede ned Test Shell If a test shell is a unit test shell for a RECURSIVE schema, then it is selected for execution from both the .pts and .nts les. { Recursive Test Shell If a test shell is a unit test shell for a REDEFINED schema, if is not executed. New Tests This is simply the selection of tests from the .nts le. Tests in the .pts le are ignored. Parent Tests This is simply the selection of tests from the .pts le. Tests in the .nts le are ignored.
K Level
This is the testing of test shells that have a schema call sequence number lest than or equal to a selected number. This number is called a \k" level and is taken from algebraic test selection methodologies. All Tests This is the selection of all tests in the .pts and .nts les.
4.5 Test Conversion The other function of the tsac program is the conversion of the selected test cases into their Eiel (or concrete) format. This is done automatically in the background and is not noticed by the user. In the current version of the system, the tsac program only generates one le containing the converted test shells. The le is named classname .ts and is automatically read in by the test harness. For a discussion on concrete test shells representation refer to the section 4.3.2 above.
5 System Under Test The test con guration and test case shells created above are then used to execute the program (Figure 4). Eiffel
Class
Eiffel
Class
Wrappers
Test Cases
Test Harness Generator
Harness
Eiffel
Test
Source
Compiler
Harness
Code
Test
Test
Harness
Results
Shell
Figure 4: System Under Test Subsystem To generate the test harness, the system must merge a preconstructed test harness shell with the code to be tested. This is then rewritten to create the source code of the nal test harness that contains the source code to be tested. The test harness will create the object under test, send it messages, inspect its state and retrieve the results of performing a test.
5.1 Class Wrappers So that the test harness may interface with the class under test two class wrapper les need to be created. The rst wrapper class is called child wrapper and the second is called the client wrapper.
6 Child Wrapper Turner and Robson (1993b) suggest that for the purpose of testing, a test class is derived from the class under test. It inherits all the features and representation from its parent class. The extra (testing) routines are then added to this class.
Class needing Testing
Sub-class Actually Tested
Attribute list
Duplicate Attribute list
Routine list
Copy-state Compare-state
Figure 5: Subclass Actually Under Test Using this suggestion, the class that is actually tested is a child of the class selected by the programmer (Figure 5). This assumes that the inheritance mechanism of the programming language is correctly implemented. This also makes the task of adding any testing routines easier, instead of 'patching' an existing class the system creates a new class. Figure 5 shows the duplicated attribute list. This is done so that a copy of the state before a message is sent, can be saved for comparing with the actual state after a message is sent. The actual copying of the state is to be performed by the added routine (shown in the diagram) and the comparing is done by another routine. The test harness calls the copy routine before calling an actual routine inherited from the class under test. The copying must be deep (i.e., a copy of the actual object not the reference) for all attributes and any attributes they contain recursively. After calling a routine from the inherited class under test, the compare routine is called. It would then verify what attributes (if any) have been mutated. As we have the exact list of attributes being mutated speci ed in our Object-Z speci cation (the list in the signature of an operational schema), the system reports back to the programmer any dierences between the attributes speci ed to change and those that actually change. It should be noted that all attributes speci ed to change need not do so. This is not automatically considered an error as the change may not occur for this particular test case, but a warning message may be generated for informational purposes. If an attribute is not speci ed to change and actually does change under testing, then an error is reported. Within our framework, the child is simply a wrapper of the state of the class under test. Currently the programmer must manually generated the child class as a le named with the same class name but having a name of \classname .cld".
6.1 Client Wrapper The next class that we look at is the client of the actual class under test. The purpose of the client class is to wrap the routine interface of the class under test. In the client class a routine called exec routine is provided that will trigger (or re) a requested routine in the class under test. A secondary task of the client class is to watch the mutation of the state variables that were wrapped in the child class. That is, the exec routine routine calls the test copy state routine before triggering a routine in the class under test. Then after the requested routine has been triggered, the test state changed and test changed attributes routines are calls to verify if and what state variables have been mutated. If state variables (attributes) in the class under test were mutated, when the speci cation says that they should not be, then an error is reported.
7 Execution Results The results of executing the test harness are produced in a report le with the name\classname.rpt". For each test the test number is given followed by the string representation of the resultant list.
For the test shells shown previously the following result le would be generated. Results for script `phone' Test #120 "true" Test #125 Exception at feature #3 `find_subscriber' Test #127 End Results
Note that test #120 producted a true result, that is because the subscriber was connected. Test #125 produced an exception when executing find subscriber because the number being looked up was not in the phone database. The third execution result is that there was no (the empty list) phone number connected for the selected subscriber.
8 Reference System The task of the reference system is to develop a parallel set of test results for comparing with the actual test results (see Section 9). We need to have a good con dence level that the reference system is correct and valid under all test cases. That is, we are going to assume that it is the test oracle. The reference system could be a prototype developed automatically from the Object-Z speci cation using re nement calculus (Figure 6). It has been chosen to not develop the prototype as a part of this research, but it will be suggested how such a prototype would be used. This is because of the large work required to develop such a system and the time required to develop a provable prototype. Object Z Specification Refined Into
Test
Prototype
Cases
System
Generates
Prototype Test Results
Figure 6: Ideal Reference Subsystem As a replacement of a generated prototype the development of a test result generation tool could be investigated (Figure 7) This would be based around using any informal test language to specify test results. The main problem with this suggestion is that the time spent developing the tool may be better spend on developing a full prototype reference system. Test
Result
Cases
Generation Tool
Test Results Semi-Automatic Generation of
Figure 7: Generation Tool Reference Subsystem As an alternative to a prototype, if the OZTEST system is being used to test ported code, then the reference system would be the original implementation of the system. Alternatively the reference system could be a second system developed independently that has already been tested (Figure 8). We will be using manual generation of results and thus would feed these into the next section for result veri cation.
Ported or Test
Parallel
Cases
Implement-
Generates
Test Results
ation
Figure 8: Implementation Reference Subsystem
9 Result Veri cation The veri cation of the results after performing the tests (Figure 9) requires that the results have been formatted for both the system under test and the reference system. In addition to matching up the values returned the test con guration data base must be referenced. Test Configuration
SUT Test Results
Reference Test Results
Result Verifier
Test Report
Figure 9: Result Veri cation Subsystem Using the test con guration data base, the result veri er will check the basic values returned from calling the behavioural routines of the class being tested. If any anomalies exist, a report would be generated containing the particular behavioural routine called, the values from the system under test and the reference system.
10 Conclusions We have described a complete testing system that would allow the tester to create, select, execute and verify test shells. As part of the guidelines to create test shells we have given a way of representing them in an abstracted form. For the selection of test shells we have described a tool and have given ve dierent types of test selection criteria Once test shells have been selected they can be executed and execution results can be collected. The OZTEST system currently handles only intra-class testing. Further research is need for inter-class testing. We also need to establish a methodology to developed a reference system directly from the speci cation. Finally research is needed so that the implementation may include inputs other than method parameters (for example, mouse movement).
References Amla, N. and P. Ammann (1992). Using z speci cations in category partition testing. In Compass 1992 Proceedings of the annual conference on computer assurance, pp. 3{10. Booch, G. (1991). Object oriented design with applications. Redwood City, California: Benjamin/Cummings.
Carrington, D. and P. Stocks (1994, February). A tale of two paradigms: Formal methods and software testing. Technical report, Software Veri cation Research Center, Department of Computer Science, The University of Queensland, Queensland 4072. Coad, P. and E. Yourdon (1991). Object-oriented design. Englewood Clis, N.J: Yourdon Press. Diller, A. (1990). Z An Introduction to Formal Methods. Chichester, West Sussex PO19 1UD, England: John Wiley and Sons Ltd. Duke, R., P. King, G. Rose, and G. Smith (1991, May). The object-z speci cation language: Version 1. Technical report, Sofware Veri cation Research Center: Department of Computer Science, The University of Queensland Queensland 4072 Australia. Fletcher, R. S. (1994, April). Testing of object-oriented software using formal speci cations. Master Thesis, Department of Software Development Monash University, 260. Kirani, S. and W. T. Tsai (1994, December). Speci cation and veri cation of object-oriented programs. Technical report, Computer Science Department, University of Minnesota, Minneapolis, MN 55455. Laycock, G. (1992, May). Formal speci cations and testing: a case study. Software Testing, Veri cation and Reliability 2 (1), 7{23. Meyer, B. (1992). Eiel The Language. Campus 400, Maylands Avenue, Hemel Hempstead, Hertfordshire, HP2 7EL: Prentice Hall International. Potter, B., J. Sinclair, and D. Till (1991). An Introduction to Formal Speci cation and Z. 66 Wood Lane End, Hemel Hempstead, Hertfordshire HP2 4RG: Prentice Hall International. Turner, C. D. and D. J. Robson (1993a, April). State-based testing and inheritance tr 1/93. Technical report, Computer Science Division, School of Engineering and Computer Science(SECS), University of Durham, Durham, England. Turner, C. D. and D. J. Robson (1993b, April). A suite of tools for the state-based testing of objectoriented programs tr-14/92. Technical report, Computer Science Division, School of Engineering and Computer Science(SECS), University of Durham, Durham, England.
A Phone Database Example In this appendix we give the Object-Z speci cation and the eiel implementation of a phone database. This is followed by the child and client wrappers used by the system under test subsystem.
A.1 Object-Z Speci cation We de ne Person as of the type string (S) and Phone as a sequence of Digit , where Digit is one of the digits 0 - 9. Person == S Digit ::= 0 j 1 j 2 j 3 j 4 j 5 j 6 j 7 j 8 j 9 Phone == seq Digit
We now de ne the class phone, representing an interface to a database of phonenumbers and subscribers. This example was addapted from the example in Diller (1990).
Phone The class phone has two state variables, the rst is a list of registered subscribers known to the system, the second is a function from telephone numbers to subscribers. There is the restriction that a subscriber for a phone number must be from the set of known subscribers. subscribers : PPerson telephones : Phone ! Person ran telephones subscribers The initial system starts o with no subscribers and no telephones connected. INIT subscribers = fg telephones = fg The operation NewSubscriber allows us to add new subscribers to the phone database. NewSubscriber (subscribers ) name ? : Person name ? 62 subscribers subscribers 0 = subscribers [ name ? telephones 0 = telephones The operation NewConnection allows us to connect a new phone number for a subscriber. NewConnection (telephones ) name ? : Person newnumber ? : Phone name ? 2 subscribers newnumber ? 62 dom telephones telephones 0 = telephones [ fnewnumber ? 7! name ?g subscribers 0 = subscribers The operation IsSubscriber tells us if a person is known to the data base. IsSubscriber subscriber ? : Person listed ! : B subscriber ? 2 subscribers ) listed ! = true subscriber ? 62 subscribers ) listed ! = false subscribers 0 = subscribers telephones 0 = telephones
The operation IsConnected tells us if a phone number is currently being used. IsConnected phonenumber ? : Phone connected ! : B phonenumber ? 2 dom telephones ) connected ! = true phonenumber ? 62 dom telephones ) connected ! = false subscribers 0 = subscribers telephones 0 = telephones The operation Disconnect will disconnect any existing phone connection. Disconnect (telephones ) phonenumber ? : Phone phonenumber ? 2 dom telephones telephones 0 = fphonenumber ?g ? C telephones subscribers 0 = subscribers The operation RemoveSubscriber will remove a known subscriber from the database only if they do not have any phones connected in their name. RemoveSubscriber (subscribers ) subscriber ? : Person subscriber ? 2 subscribers subscriber ? 62 ran telephones subscribers 0 = subscribers n subscriber ? telephones 0 = telephones The operation FindPhone returns the set of phones numbers allocated to a known subscriber. FindPhones subscriber ? : Person phonenumbers ! : PPhones subscriber ? 2 telephones phonenumbers ! = dom(telephones B fsubscriber ?g) subscribers 0 = subscribers telephones 0 = telephones
The operation FindSubscriber returns the subscriber of a phone number. FindSubscriber phonenumber ? : Phone subscriber ! : Person phonenumber ? 2 dom telephones subscriber ! = telephones (phonenumber ?) subscribers 0 = subscribers telephones 0 = telephones
A.2 Eiel Implementation class PHONE creation make feature -- exported features make is
-- create an empty phone database with capacity for 200 -- connections.
do !! subscribers.make; !! telephones.make(200); end; new_subscriber (name_in: STRING) is -- add a new subscriber to the phone database require subscriber_unknown: not is_subscriber(name_in) do subscribers.put_front(name_in); ensure is_subscriber(name_in) end; new_connection (name_in: STRING; new_number_in: STRING) is -- connect a new phonenumber for a subscriber require subscriber_known: is_subscriber(name_in); unused_number: not is_connected(new_number_in); do telephones.put(name_in,new_number_in); ensure is_connected(new_number_in); end; is_subscriber (subscriber_in: STRING): BOOLEAN is -- is the subscriber known? do Result := false; from subscribers.start until subscribers.off loop if equal(subscriber_in,subscribers.item) then Result := true; end; subscribers.forth; end; end; is_connected (phone_number_in: STRING): BOOLEAN is -- is the phone number currently being used?
do
Result := telephones.has(phone_number_in);
end; disconnect (phone_number_in: STRING) is -- disconnect this existing phone connection require is_connected: is_connected(phone_number_in) do telephones.remove(phone_number_in); ensure not is_connected(phone_number_in) end; remove_subscriber (subscriber_in: STRING) is -- remove a known subscriber require known_subscriber: is_subscriber(subscriber_in) do from subscribers.start until equal(subscribers.item,subscriber_in) loop subscribers.forth end; subscribers.remove ensure not is_subscriber(subscriber_in) end; find_phones (subscriber_in: STRING): LINKED_LIST[STRING] is -- return the set of phone numbers for this known subscriber require known_subscriber: is_subscriber(subscriber_in) local phone_numbers_out: LINKED_LIST[STRING] do !! phone_numbers_out.make; from telephones.start until telephones.off loop if equal(telephones.item_for_iteration,subscriber_in) then phone_numbers_out.put_front(telephones.key_for_iteration) end; telephones.forth end; Result := phone_numbers_out; end; find_subscriber (phone_number_in: STRING): STRING is -- return the subscriber of a known phone number require known_connection: is_connected(phone_number_in) do Result := telephones.item(phone_number_in); end; feature {NONE} -- state -- the subscriber set is stored as a linked list. subscribers: LINKED_LIST[STRING]; -- the telephones function is stored as a hash table -- of string subscribers, using a string of numbers as the key. telephones: HASH_TABLE[STRING,STRING]; end -- class PHONE
A.3 Child Wrapper File class OZTEST_CUT inherit PHONE creation make feature test_copy_state is -- Deep clone all state variables of A_CLASS do test_subscribers := deep_clone(subscribers); test_telephones := deep_clone(telephones); end; test_state_changed : BOOLEAN is -- See if the state has changed from last copy state local do if not deep_equal(test_subscribers, subscribers) then Result := true; elseif not deep_equal(test_telephones, telephones) then Result := true; else Result := false; end; end; test_changed_attributes: LINKED_LIST[STRING] is -- Return the names of attributes that have changed state local test_change_list: LINKED_LIST[STRING]; do !! test_change_list.make; if not deep_equal(test_subscribers, subscribers) then test_change_list.put_front("subscribers") end; if not deep_equal(test_telephones, telephones) then test_change_list.put_front("telephones") end; Result := test_change_list end feature {NONE} test_subscribers : LINKED_LIST[STRING]; test_telephones : HASH_TABLE[STRING,STRING]; end -- class OZTEST_CUT
A.4 Client Wrapper File run (routine_name: STRING; parameters: LINKED_LIST[LINKED_LIST[STRING]]) : LINKED_LIST[STRING] is -- call the selected routine do if equal(routine_name,"new_subscriber") then test_new_subscriber(parameters); elseif equal(routine_name,"new_connection") then test_new_connection(parameters); elseif equal(routine_name,"is_subscriber") then Result := test_is_subscriber(parameters); elseif equal(routine_name,"is_connected") then Result := test_is_connected(parameters); elseif equal(routine_name,"disconnect") then test_disconnect(parameters); elseif equal(routine_name,"remove_subscriber") then test_remove_subscriber(parameters); elseif equal(routine_name,"find_phones") then
end;
Result := test_find_phones(parameters); elseif equal(routine_name,"find_subscriber") then Result := test_find_subscriber(parameters); else put_routine_error_message(routine_name); end;
test_new_subscriber (parameters :LINKED_LIST[LINKED_LIST[STRING]]) is -- call new_subscriber do class_under_test.new_subscriber( string_parameter( single_parameter_list(1,parameters))); end; test_new_connection (parameters :LINKED_LIST[LINKED_LIST[STRING]]) is -- call new_connection do class_under_test.new_connection( string_parameter( single_parameter_list(1,parameters)), string_parameter( single_parameter_list(2,parameters))); end; test_is_subscriber (parameters :LINKED_LIST[LINKED_LIST[STRING]]) : LINKED_LIST[STRING] is -- call is_subscribers local boolean_result: BOOLEAN; return: LINKED_LIST[STRING]; do boolean_result := class_under_test.is_subscriber( string_parameter( single_parameter_list(1,parameters))); !! return.make return.put_front(boolean_result.out); Result := return; end; test_is_connected (parameters :LINKED_LIST[LINKED_LIST[STRING]]) : LINKED_LIST[STRING] is -- call is_connected local boolean_result: BOOLEAN; return: LINKED_LIST[STRING]; do boolean_result := class_under_test.is_connected( string_parameter( single_parameter_list(1,parameters))); !! return.make return.put_front(boolean_result.out); Result := return; end; test_disconnect (parameters :LINKED_LIST[LINKED_LIST[STRING]]) is -- call disconnect do class_under_test.disconnect( string_parameter( single_parameter_list(1,parameters))); end;
test_remove_subscriber (parameters :LINKED_LIST[LINKED_LIST[STRING]]) is -- call remove_subscriber do class_under_test.remove_subscriber( string_parameter( single_parameter_list(1,parameters))); end; test_find_phones (parameters :LINKED_LIST[LINKED_LIST[STRING]])
: LINKED_LIST[STRING] is -- call find_phones do
end;
Result := class_under_test.find_phones( string_parameter( single_parameter_list(1,parameters)));
test_find_subscriber (parameters :LINKED_LIST[LINKED_LIST[STRING]]) : LINKED_LIST[STRING] is -- call find_subscriber local string_result: STRING; return: LINKED_LIST[STRING]; do string_result := class_under_test.find_subscriber( string_parameter( single_parameter_list(1,parameters))); !! return.make return.put_front(string_result); Result := return; end;