Background of the Invention
The present invention relates, in general, to software engineering, and more particularly to testing of object oriented software.
According to the prior art, testing of software modules has taken place only on an individual module by module basis. When using structured software the basic unit is the function. Traditional tests of such software supply a set of inputs, execute the function, and examine the outputs to verify that correct results were obtained. Functions are assumed to maintain no state information that is used during subsequent executions. More recently use of object oriented programming methodologies has become common. The special requirements of object oriented programming are outlined in a paper entitled "What is Object oriented Programming?", by Bjarne Stoustrup, published May 1988 in IEEE Software, which is included herein by reference. Another explanation related to C++ is found on pages 127-200 of the "Turbo C++ User's Guide", Second Edition, published in 1991 by Borland International, Inc., which is included herein by reference.
For object-oriented software, structural testing is inadequate because the internal state of objects is not tested. The basic unit in object-oriented software is the class. Classes are tested by setting an initial state, executing one or more class operations, and verifying that the final state is correct. However, prior art practices only consider those data members defined in a class to be a part of the class' state. In actuality, the state of a class consists of data members defined within the class and those that are inherited. Therefore, prior art testing methods do not verify the complete state of an object.
There is a need for a method to test the complete state of an object within an object oriented program. The method should be easily automated and verify the state of all levels of the inheritance hierarchy. Ideally the method should minimize the effort required and allow the resultant object code to be unchanged as a result of the testing.
Brief Description of the Drawing
The single figure shows a flow chart which illustrates the major steps of a method which is in accordance with a preferred embodiment of the present invention.
Detailed Description of the Drawing
The single figure shows a flow chart which illustrates the major steps of a method as a preferred embodiment in accordance with the present invention. For simplicity the preferred embodiment is described using C++ terms and conventions, however other embodiments use different object oriented programming languages. A hierarchy of classes under test is defined 11 by creating an object oriented software system. Every class in the system becomes a class under test during the software testing process. To simplify testing, each class under test should have an assignment operator (=), a comparison operator (==), and an output operator (<<). All stream based class under test member functions and operators should receive a stream as a parameter to allow the testing software to control inputs and review outputs. Constant values should have publicly available definitions usable by the testing software. When there is a restricted number of values that a class under test data member may take, the legal value set should be specified using an enumerated type to enhance boundary testing. Each class under test should also have a constructor with no parameters (the test software will define the state of each object). Finally, every class under test should contain a copy constructor. Note that the above items are not required in order for the testing strategy to work, but they greatly simplify the task of automating test code generation and test vector creation.
A test class hierarchy is defined 12, wherein each class under test has a corresponding test class. The test class hierarchy duplicates the inheritance hierarchy of the classes under test. Four goals should be met when defining a test class. First, the test class architecture must be generic to accommodate automation. Second, the testing intelligence must appear in the test data, not the test code. Thus, when a new test is required, the user need only add more test data rather than change the test code. Third, a test class should be as non-intrusive as possible, meaning that changes to the class under test in order to accommodate the test class must be minimized. Lastly, the test class must have complete access to it's associated class under test in order to control and observe the classes under test's behavior.
A C++ coded example of classes under test and appropriate test classes is shown below:
Each test class contains a "test" member function that performs all of the tests to be conducted on it's associated class. Each test class also contains set-state and verify-state member functions. The set-state member function is used to assign values to the elements related to an object in order to initialize the object to a known state. At least one test vector is defined 13 for each class under the test member function. Each test vector comprises an initial complete state, an expected complete state, a set of function inputs, and a set of expected results for the member function being tested. The verify state member function is used to compare the current values of an objects data elements to an expected set of values. Each test class is declared to be a "friend" of its associated class under test. In C++, this gives the test class access to private data elements contained in the class under test. This association allows the member functions of the test class to read and write all data elements contained in the class under test.
The testing is initiated by calling 18 the test member function of the class under test. The test proceeds by setting 17 an initial complete state of the test class. Setting 17 the initial complete state is achieved by implicitly calling the set state member functions through the inheritance structure of the test classes. Next, a member function of the class under test is executed 14.
The results obtained are implicitly compared 18 to the expected results. When a difference between the current state and expected state of an object is detected, the return value of the verify state member function is set to indicate an error condition. This return value propagates back through the inheritance hierarchy to the leaf test class. Steps 17, 14, and 18 are repeated for each member function 15 of that test class and for each test vector 16 of each member function.
For example, to test the functionality of class B (which inherits from class A), the test() member function in class B.sub.-- test is called. The B.sub.-- test::test() member function looks like the following pseudo code:
The pseudo code shows how the testing methodology works when testing the b.sub.-- compute member function of class B. A set of test vectors is prepared with each vector containing an initial state, a set of inputs for the b.sub.-- compute member function, an expected state, and an expected return value. The initial state and expected state not only contain values for the data elements defined in class B, but also for those defined in class A. For every test vector, the initial state of an object is set, the b.sub.-- compute function is executed, and the final state and return condition are verified.
The goal when calling B.sub.-- test::test() is to verify the functionality and complete object state of every member function defined within class B (represented by the statement "rtn<-B::b.sub.-- compute(input)" above). Basically, if class B has three member functions, then there needs to be three sets of vectors that contain the initial state, expected state and expected return for different invocations of that member function. So, when B.sub.-- test::test()is called, for every member function in class B and for each test vector defined by the user, B.sub.-- test::set.sub.-- state() is called to set the initial complete state of class B. Since B inherits from A, B.sub.-- test::set.sub.-- state() calls A.sub.-- test::set.sub.-- state(). Technically this call is explicit (look at the pseudo code above in B.sub.-- test::set.sub.-- state()), but it can be referred to as an implicit call because a automated test generator can analyze the inheritance hierarchy and know that B.sub.-- test::set.sub.-- state() must call A test::set.sub.-- state() and could simply generate that piece of code.
After the statement "rtn<-B::b.sub.-- compute(input)" is called, B.sub.-- test::verify.sub.-- state() is called. Here again, B.sub.-- test::verify.sub.-- state() "implicitly" calls A.sub.-- test::verify.sub.-- state() because to verify whether or not the call to B::b.sub.-- compute (in the statement "rtn <-B::b.sub.-- compute(input)") met the test specification, the complete state of class B needs to be verified, requiring the state of class A to be verified (through A.sub.-- test::verify.sub.-- state()).
An alternative embodiment in accordance with the present invention uses a conditional compilation process to include the test class hierarchy during software development, but to exclude the test class hierarchy for the final software. In this way the automated testing does not affect the final object code which is delivered to a user.
By now it should be clear that the present invention provides a method to test the complete state of an object within object oriented software. The method is easily automated and can verify the state of all levels of the inheritance hierarchy. The method minimizes the effort required and allows the resultant object code to be unchanged as a result of the testing.
While specific embodiments of the present invention have been shown and described, further modifications and improvements will occur to those skilled in the art. It is understood that the invention is not limited to the particular forms shown and it is intended for the appended claims to cover all modifications which do not depart from the spirit and scope of this invention.