Testing, part 2 Course notes for CEN 4010
OO Testing • Testing objects in isolation • Inheritance • Aggregation • Objects as parameters • State • Destruction • Self diagnosis
Testing Objects in Isolation • Data Members • Allowed range vs. Disallowed range • Boundary values (v-1, v, v+1) • Can data members be accessed directly? • Operations • Arguments • Allowed range • Boundary values • Establish equivalence partitions
Testing Objects in Isolation • Operations (continued) • Construction • Proper initialization of all data members • Proper defaults if value not specified • Destruction • Proper deallocation of memory • Set & Get operations • Is operation doing defensive checks to catch invalid data?
Inheritance: Overview • Liskov Substitution Principle • If an object of type D can be substituted for an object of type B, and program P’s behavior is unchanged, then D is a subtype of B. • Everything that is true of each superclass is also true for the subclass. • You cannot NOT inherit data members or member functions from a superclass. • You cannot NOT inherit changes made to a superclass!
The Fragile Superclass Problem • Consider class CheckingAccount which is derived from class InterestBearingAccount: class InterstBearingAccount {public: float iRate; float GetBalance() const; //constraint: balance >= $0.00 }; class CheckingAccount : public InterestBearingAccount {public: float FutureValue(UINT NoYears) { return pow(GetBalance(), ....... ); } • What happens to FutureValue()if GetBalance()can return a negative number?
Order of Testing Inheritance • Across Classes: • Test superclasses before subclasses. • The superclass is the foundation for the subclass! • Test superclass functions visible through subclass • Test functions over-ridden in derived class. • Within a Derived Class: • Test object construction for proper initialization & assignment of data members. • superclass constructor • embedded class construction • Test new functions in derived class.
Testing Abstract Base Classes • Consider: class Employee { public: //...other member functions... virtual Paystub GeneratePay( ) = 0; //must define in subclasses }; class SalariedEmployee : public Employee { public: //... Paystub GeneratePay( ) {....; } }; • How do I test the abstract class Employee?
Testing Abstract Base Classes • Option 1: • Temporarily convert Employee to a concrete class and test it in isolation: class Employee { public: //... void OtherFunction( ); virtual Paystub GeneratePay( ) //=0 {::Messagebox(“EmployeePay()”...) ; ...;} }; Employee emp(......); //create instance of formerly abstract class emp.GeneratePay( ); //test NON-PURE member function emp.OtherFunction( ); //test other Employee member functions....
Testing Abstract Base Classes • Option 2: • Temporarily convert Employee to a concrete base class and test it in context: class Employee { public: //... virtual Paystub GeneratePay( ) //=0 {::Messagebox(“EmployeePay()”...) ; ...;} }; SalariedEmployee sEmp(......); //SalariedEmployee::GeneratePay commented-out sEmp.GeneratePay( ); //test NON-PURE member function of base class.
Which Subclass Functions Must Be Tested? Base • Consider two classes, Base and Derived. • Base has function nochange() and overridden(). • Derived overrides and redefines the function overridden(). • Must test Derived:overridden( )—it’s new code. • Must I test Base::nochange( )? • Yes, if Base::nochange( ) calls or uses a return value from overridden( ), [which may now actually be the Derived::nochange( ) function using the Derived::overridden( ) function.] • Derived::overridden( ) may return a superset of values beyond those returned by Base::overridden(). nochange() overridden() Derived overridden()
Reusing base class tests in a derived class? • Base::overridden( ) and Derived::overridden( ) are two different functions. • But their sets of test requirements will overlap. • You only need to write new tests for those Derived::overridden( ) requirements that are not satisfied by the Base::overridden( ) tests. • Notes: • You have to apply the Base::overridden( ) tests to objects of class "derived". The test inputs may be the same for both classes, but the expected results might differ in the Derived class. WHY?
Testing Overridden Functions • Look for: • Differences in argument ranges • Subclass function may have a narrower range than overridden superclass function. • Differences in return values • If subclass returns a larger range, existing programs may not behave properly—may die! • Proper class::function is called • Especially important in C++ • (static type != dynamic type)
Testing: Aggregation • Test Order • From containing class to contained classes. • Will require writing stub classes/functions to test containing classes. • From contained classes to containing class. • Will require writing driver functions to test contained classes. • Protocol Test • Test direction of any function protocols • Does embedded object know it is contained? • Does embedded object’s behavior change because it is contained? • Do aggregate and embedded object converse bi-directionally? • Does aggregate regard embedded objects as mere servers?
Testing: Aggregation • State Test • What are dependencies of aggregate’s state upon the embedded objects’ states? • Derived from STD’s. • Test that each state for aggregate is properly mapped into each embedded object. • E.g. if the Vehicle is in the ON state, is each embedded object also ON, or at least ENABLED? • Test that each state for each embedded object is properly mapped into the aggregate’s state. • E.g. if the Headlights are broken, is the Vehicle still functional in a degraded mode?
Aggregate as an Agent, or Broker • Suppose a class ApplianceServicer wants to check the status of the OverHeadProjector’s PowerSupply. Should OverHeadProjector have a member function: GetPowerSupply( )? class OverHeadProjector {public: //....... PowerSupply * GetPowerSupply( ); private: Fan f; Base b; FocusingLens fl; PowerSupply* ps; };
Aggregate as a Broker • If we provide the GetPowerSupply( ) function in OverHeadProjector, then we allow ApplianceServicer to have unrestricted access to PowerSupply. • In this scenario OverHeadProjector is acting as a broker.
Aggregate as a Broker • Drawbacks: • requestor objects can change state of member object without the containing object knowing this. • the requestor could delete the member object without the containing object knowing this. (The double delete of the memory might crash the application.) • The broker object reveals its implementation when it “matches-up” the requestor and the member object. • Advantages: • Aggregate maintains a simplified interface.
Aggregate as an Agent • If OverHeadProjector acts as an “agent” for the PowerSupply, it provides a surrogate interface(e.g., GetPowerSupplyStatus() ) to a requestor object. • Requestor never interfaces with PowerSupply directly.
Aggregate as an Agent • Drawbacks: • Aggregate object must provide a surrogate interface for each member object function to be “exported”. • Performance may suffer. • Maintenance is complicated as the member objects evolve new member functions. • Advantages: • Aggregate object maintains complete control of member object (no multiple deletes; change of state). • Member objects are fully encapsulated and hidden (implementation is not visible).
Testing: Aggregation • Aggregate as Broker • Test that embedded objects notify aggregate when they are destroyed. • Requires embedded object constructor that takes “owner” as argument. • Test that embedded objects provide interfaces for aggregate to determine their state. • E.g. “changed by requestor”, “disabled”, “enabled”
Testing: Aggregation • Aggregate as Agent • Test that each function exported from embedded object has been identified and can be called regardless of aggregate’s state. • E.g. I should be able to ask for the PowerSupply’s state even if the aggregate does not yet have a PowerSupply.
Static & Dynamic Types: “Slicing” • Consider the following classes:....and the following function: class Window void printNameAndDisplay (Window w) {public: { const char * name( ) const; cout << w.name( ); virtual void display( ) const; w.display( ); }; } class BorderedWindow : public Window {public: virtual void display( ) const; }; • What happens when we call this function with a BorderedWindow object? BorderedWindow bw; printNameAndDisplay(bw); [after MEYE92]
“Slicing”: Pass-by-Value • Parameter w is constructed as a Window object (note: it is passed by value). • All the information specific to BorderedWindow is sliced off parameter w. • Inside printNameAndDisplay( ), w will always act like a Window object. • The call to display( ) will always call Window.display( ), not the BorderedWindow.display( ) function—despite the fact that display( ) is virtual!
“Slicing”: through Assignment • Consider: class String { //...... }; class Filename: public String { //... }; • If we have a String s and a Filename f, what happens in the following code? s = f ; • The Filename portion of f is sliced off, and the String portion of f is assigned to s.[KOEN96]
Representing State • Example 1: • A Person object can pass through the marital states: Single, Engaged, Married, Divorced, Widowed. • How should we represent marital state in a Person object? • Example 2: • Water can exist in three common physical states: Solid, Liquid, Vapor. • How should we represent physical state in a Water object?
State: Enumerated Attributes class Person {public: enum maritalState {SINGLE, ENGAGED, MARRIED, DIVORCED, WIDOW}; ..... GoOutWithOppositeSex(); GoShopping(); PayBills(); .... }; • Valid technique if normal Person-functions do not experience “gross” changes in behavior as Person changes maritalState. • Otherwise, you will die on all the switch (maritalState).... statements.
Testing: Enumerated States • Use State Transition Diagrams. • Select object & the subset of class states it will transit. • Different construction ð different initial states. • Test transition of object into and out of each selected state. • Test for proper behavior of each function in each selected state. • Attempt improper behavior in each selected state.
Testing Object Destruction:Object Ownership • Object Ownership • Test that each object has an “owner” who destroys it appropriately. • Test that objects which commit suicide do so properly. • The method of suicide must conform to the method of object construction.
Testing Object Destruction:Clean-up Functions • Verify as appropriate that an object: • Cleans-up itself into a state in which its destructor can safely be invoked. • E.g. a collection may need to direct its components to store themselves in a database before it (and they) are destroyed. • Propagates the “clean-up” to other objects under its control before they are destroyed. • Has client objects which properly observe and invoke its clean-up protocol.
Self diagnosis: Is_Valid( ) • Defensive technique for detecting internal inconsistencies. class BusinessObject {public: virtual boolean Is_Valid() const = 0; .... }; class GeneralInvoice : public BusinessObject {private: Date dt; String name; float Amount; public: virtual boolean Is_Valid() const { return (name.Is_Valid() && dt.Is_Valid() && Amount <1000) } }; interface BusinessObject { boolean Is_Valid(); .... } class GeneralInvoice implements BusinessObject {private Date dt; private String name; private float Amount; public boolean Is_Valid( ) { return (name.Is_Valid() && dt.Is_Valid() && Amount <1000) } }
Self diagnosis: Dump( ) • Service function to force dump of instance variables. class BusinessObject {public: virtual void Dump( ) const = 0; .... }; class GeneralInvoice : public BusinessObject {private: Date dt; String name; float Amount; public: virtual void Dump() const { DumpStream ds(“MyDumpStreamFile”); ds << “GeneralInvoice” << objID << “Date:” << dt <<“Name:” <<name << “Amount:” << Amount << endl; } };