320 likes | 458 Views
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)
E N D
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; } };