Implementing a New Verification Point |
QualityArchitect provides a framework for implementing COM/DCOM verification points. This framework includes interfaces for all of the components you must implement, as well as a VPFramework
component that provides much of the required implementation for all verification points.
Any verification point type that you implement should inherit the VPFramework
component's implementation. Since COM does not support implementation inheritance, you acccomplish this task through COM containment. With COM containment, methods and properties inherited from the framework appear as part of your verification point type. This minimizes the amount of code that a test designer has to write to perform a simple verification point, plus it eliminates the need for a QueryInterface operation.
For more information, see Essential COM by Don Box.
To implement a new verification point type, you must implement the interfaces and components documented in the following sections:
Your verification point component's interface must contain properties or methods for defining the verification point. This interface must inherit from the IVerificationPoint
interface -- for example:
[ object, uuid(7C4870B0-6E1A-11D4-9A26-0010A4E86989), dual, helpstring("IDatabaseVP Interface"), pointer_default(unique) ] interface IDatabaseVP : IVerificationPoint { [propget, helpstring("property ConnectionString")] HRESULT ConnectionString([out, retval] BSTR *pVal); [propput, helpstring("property ConnectionString")] HRESULT ConnectionString([in] BSTR newVal); [propget, helpstring("property SQL")] HRESULT SQL([out, retval] BSTR *pVal); [propput, helpstring("property SQL")] HRESULT SQL([in] BSTR newVal); };
Your specialized verification point must perform the following tasks:
To enable your specialized Verification Point component to perform these tasks, you must implement the following interfaces:
IVerificationPoint
interface (because your Verification Point interface inherits from this interface).
IPersistFile
interface.
IVPFramework
interface (because the IVerificationPoint
interface inherits from IVPFramework
).
Your IVPFramework
methods should pass the calls through to a contained VPFramework
object, thus inheriting the implementation through containment.
Here is an example class declaration for a Verification Point component:
class ATL_NO_VTABLE CDatabaseVP : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CDatabaseVP, &CLSID_DatabaseVP>, public IDispatchImpl<IDatabaseVP, &IID_IDatabaseVP, &LIBID_RTCOMVPLib>, public IPersistFile { public: CDatabaseVP() { } DECLARE_REGISTRY_RESOURCEID(IDR_DATABASEVP) DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct(); BEGIN_COM_MAP(CDatabaseVP) COM_INTERFACE_ENTRY(IDatabaseVP) COM_INTERFACE_ENTRY(IVerificationPoint) COM_INTERFACE_ENTRY2(IDatabaseVP, IDispatch) COM_INTERFACE_ENTRY(IPersistFile) END_COM_MAP() // IVerificationPoint public: STDMETHOD(CodeFactoryGetConstructorInvocation) (CTDScriptTypes Language, BSTR * Code); STDMETHOD(CodeFactoryGetNumExternallizedInputs) (CTDScriptTypes Language, short * NumInputs); STDMETHOD(CodeFactoryGetExternalizedInputDecl) (CTDScriptTypes Language, short InputNumber, BSTR * Code); STDMETHOD(CodeFactoryGetExternalizedInputInit)(/*[in]*/ CTDScriptTypes Language, /*[in]*/ short InputNumber, /*[out, retval]*/ BSTR *Code); STDMETHOD(CodeFactoryGetNumPropertySet)(/*[in]*/ CTDScriptTypes Language, /*[out, retval]*/ short *NumProps); STDMETHOD(CodeFactoryGetPropertySet)(/*[in]*/ CTDScriptTypes Language, /*[in]*/ short InputNumber, /*[out, retval]*/ BSTR *Code); STDMETHOD(DefineVP)(); // IDatabaseVP public: STDMETHOD(get_SQL)(/*[out, retval]*/ BSTR *pVal); STDMETHOD(put_SQL)(/*[in]*/ BSTR newVal); STDMETHOD(get_ConnectionString)(/*[out, retval]*/ BSTR *pVal); STDMETHOD(put_ConnectionString)(/*[in]*/ BSTR newVal); // IPersistFile public: STDMETHOD(GetCurFile)(LPOLESTR *ppszFileName); STDMETHOD(SaveCompleted)(LPCOLESTR pszFileName); STDMETHOD(Save)(LPCOLESTR pszFileName, BOOL fRemember); STDMETHOD(Load)(LPCOLESTR pszFileName, DWORD dwMode); STDMETHOD(IsDirty)(); STDMETHOD(GetClassID)(CLSID *pClassID); // IVPFramework public: // STDMETHOD(InitializeFramework)(BSTR Name, BSTR VPComparator, BSTR VPData, BSTR VPDataProvider, BSTR VPDataRenderer); STDMETHOD(PerformTest)(/*[in]*/ VARIANT Object, /*[optional, in]*/ VARIANT ExpectedData, /*[optional, in]*/ VARIANT ActualData, /*[out, retval]*/ enum VPResult *Result); STDMETHOD(get_VPname)(/*[out, retval]*/ BSTR *pVal); STDMETHOD(put_VPname)(/*[in]*/ BSTR newVal); STDMETHOD(get_Options)(/*[out, retval]*/ VARIANT *pVal); STDMETHOD(put_Options)(/*[in]*/ VARIANT newVal); STDMETHOD(get_VP)( IDispatch ** ppVP ); STDMETHOD(put_VP)( IDispatch * pVP ); STDMETHOD(get_Plumbing)( IDispatch ** ppPlumbing ); STDMETHOD(put_Plumbing)( IDispatch * pPlumbing ); STDMETHOD(get_CodeFactorySuffix)( BSTR* pVal ); STDMETHOD(put_CodeFactorySuffix)( BSTR newVal ); private: HRESULT CalcIsDefined(); CComPtr<IVPFramework> m_pFramework; LONG lOptions; _bstr_t bstConnectionString; _bstr_t bstSQL; };
This section describes the implementation of the following parts of the IVerificationPoint
interface:
The DefineVP()
method in the IVerificationPoint
interface invokes a UI to capture the metadata definition of the verification point. When the tester dismisses the UI, this method should populate the verification point object with the metadata it captured.
The method should return S_OK
if the metadata was successfully captured, and E_VP_UNDEFINED
if it was not -- for example:
STDMETHODIMP CDatabaseVP::DefineVP() { ... // Invoke some GUI to capture the VP's definition, in this case, // the QueryBuilder. ... pQB->DoModal(); pQB->get_Accepted( &vAccepted ); if ( vAccepted.vt == VT_BOOL && vAccepted.boolVal == VARIANT_TRUE ) { pQB->get_Connection( &vConnection ); pQB->get_SQL( &vSQL ); if ( vConnection.vt == VT_BSTR && vSQL.vt == VT_BSTR ) { bstConnectionString = vConnection.bstrVal; bstSQL = vSQL.bstrVal; } else { bDefined = false; } if ( *((LPCWSTR)bstConnectionString) == L'\0' || *((LPCWSTR)bstSQL) == L'\0' ) { bDefined = false; } } else // User canceled, or internal error in Query Builder component. { bDefined = false; } if ( bDefined == false ) return E_VP_UNDEFINED; else return S_OK; }
The code factory methods generate source code that is capable of creating instances of your verification point type.
The code factory methods are similar in function to ActiveX controls that provide additional design-time behavior that integrates with the Visual C++ or Visual Basic environments.
The QualityArchitect code generator uses the code factory methods to insert verification points into generated test scripts. If a tester using QualityArchitect wants to insert one of your verification points into a generated test script, the QualityArchitect code generator creates an instance of your verification point, and then calls the DefineVP()
method to present the tester with the UI you have created. Once the tester supplies the metadata through your UI, the code generator invokes the code factory methods to return source code. When the returned source code is inserted into the test script, a verification point is created from the metadata that the tester supplied.
For information about how the QualityArchitect code generator uses the code factory methods, see Integrating Your Verification Point with QualityArchitect.
Following are the code factory methods that you implement:
CodeFactoryGetConstructorInvocation()
returns a line of code that invokes your verification point's constructor. The code is syntactically correct for the given language.
CodeFactoryGetNumExternalizedInputs()
returns the number of externalized input variables required for code generation of this verification point.
CodeFactoryGetExternalizedInputDecl()
returns a line of code that declares the specified externalized input for this verification point. The code is syntactically correct for the given language.
CodeFactoryGetExternalizedInputInit()
returns a line of code that initializes the specified externalized input for this verification point. The code is syntactically correct for the given language.
CodeFactoryGetNumPropertySet(
) returns the number of property-set calls required to fully specify the metadata for this verification point.
CodeFactoryGetPropertySet()
returns a line of code that sets the specified property for this verification point. The code is syntactically correct for the given language.
There are also CodeFactorySuffix
property methods. The framework implements these methods -- you only pass them through. You use the suffix when constructing the externalized variables that are returned by the following methods:
CodeFactoryGetPropertySet()
CodeFactoryGetExternalizedInputDecl()
CodeFactoryGetExternalizedInputInit()
The suffix ensures that externalized variable names from multiple verification points in the same scope are unique. If the QualityArchitect code generator sets the suffix, append the suffix to each externalized variable that is declared, initialized, and set by these methods. This allows the Quality Architect code generator to insert more than one verification point into a test script without risk of variable name conflicts.
The following code listing illustrates the use of the code factory methods:
STDMETHODIMP CDatabaseVP::get_CodeFactorySuffix(BSTR *pVal) { return m_pFramework->get_CodeFactorySuffix(pVal); } STDMETHODIMP CDatabaseVP::put_CodeFactorySuffix(BSTR newVal) { return m_pFramework->put_CodeFactorySuffix(newVal); } STDMETHODIMP CDatabaseVP::CodeFactoryGetConstructorInvocation(CTDScriptTypes Language, BSTR * Code) { if (Code == NULL) return E_POINTER; // Create a line of code which constructs this type of VP. _bstr_t bsCode; BSTR bsName = NULL; BSTR bsSuffix = NULL; get_VPname(&bsName); get_CodeFactorySuffix(&bsSuffix); if ( bsName == NULL ) { return E_INVALIDARG; } switch ( Language ) { case CTD_SCRIPTTYPE_VB: // VB bsCode = L"Dim "; bsCode += bsName; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L" As New DatabaseVP"; break; default: return E_INVALIDARG; } *Code = bsCode.copy(); return S_OK; } STDMETHODIMP CDatabaseVP::CodeFactoryGetNumExternallizedInputs(CTDScriptTypes Language, short * NumInputs) { if (NumInputs == NULL) return E_POINTER; // Only VB is supported currently if (Language != CTD_SCRIPTTYPE_VB) return E_INVALIDARG; if ( lOptions == 0 ) *NumInputs = 3; else *NumInputs = 4; return S_OK; } STDMETHODIMP CDatabaseVP::CodeFactoryGetExternalizedInputDecl(CTDScriptTypes Language, short InputNumber, BSTR * Code) { if (Code == NULL) return E_POINTER; // Only VB is supported currently if (Language != CTD_SCRIPTTYPE_VB) return E_INVALIDARG; _bstr_t bsCode; BSTR bsSuffix = NULL; get_CodeFactorySuffix(&bsSuffix); switch ( InputNumber ) { case 1: // VPName bsCode = L"Dim VPname"; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L" As String"; break; case 2: // Connection String bsCode = L"Dim VPConnectString"; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L" As String"; break; case 3: // SQL Statement bsCode = L"Dim VPSQL"; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L" As String"; break; case 4: // Options bsCode = L"Dim VPOptions"; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L" As Integer"; break; default: break; } *Code = bsCode.copy(); return S_OK; } STDMETHODIMP CDatabaseVP::CodeFactoryGetExternalizedInputInit(/*[in]*/ CTDScriptTypes Language, /*[in]*/ short InputNumber, /*[out,retval]*/ BSTR *Code) { if (Code == NULL) return E_POINTER; // Only VB is supported currently if (Language != CTD_SCRIPTTYPE_VB) return E_INVALIDARG; _bstr_t bsCode; BSTR bsName = NULL; BSTR bsSuffix = NULL; get_VPname(&bsName); get_CodeFactorySuffix(&bsSuffix); if ( bsName == NULL ) { // TODO: Enum this error condition! return E_INVALIDARG; } switch ( InputNumber ) { case 1: // VPname bsCode = L"VPname"; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L" = \""; bsCode += bsName; bsCode += L"\""; break; case 2: // Connection String bsCode = L"VPConnectString"; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L"= \""; bsCode += bstConnectionString; bsCode += L"\""; break; case 3: // SQL Statement bsCode = L"VPSQL"; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L" = \""; bsCode += bstSQL; bsCode += L"\""; break; case 4: // Options bsCode = L"VPOptions"; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L" = "; bsCode += L"VPOptions"; break; default: break; } *Code = bsCode.copy(); return S_OK; } STDMETHODIMP CDatabaseVP::CodeFactoryGetNumPropertySet(/*[in]*/ CTDScriptTypes Language, /*[out, retval]*/ short *NumProps) { return CodeFactoryGetNumExternallizedInputs(Language, NumProps); } STDMETHODIMP CDatabaseVP::CodeFactoryGetPropertySet(/*[in]*/ CTDScriptTypes Language, /*[in]*/ short InputNumber, /*[out, retval]*/ BSTR *Code) { if (Code == NULL) return E_POINTER; // Only VB is supported currently if (Language != CTD_SCRIPTTYPE_VB) return E_INVALIDARG; _bstr_t bsCode; BSTR bsName = NULL; BSTR bsSuffix = NULL; get_VPname(&bsName); get_CodeFactorySuffix(&bsSuffix); if ( bsName == NULL ) { // TODO: Enum this error condition! return E_INVALIDARG; } switch ( InputNumber ) { case 1: // VPname bsCode = bsName; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L".VPname = "; bsCode += L"VPname"; if ( bsSuffix ) bsCode += bsSuffix; break; case 2: // Connection String bsCode = bsName; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L".ConnectionString = "; bsCode += L"VPConnectString"; if ( bsSuffix ) bsCode += bsSuffix; break; case 3: // SQL Statement bsCode = bsName; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L".SQL = "; bsCode += L"VPSQL"; if ( bsSuffix ) bsCode += bsSuffix; break; case 4: // Options bsCode = bsName; if ( bsSuffix ) bsCode += bsSuffix; bsCode += L".Options = "; bsCode += L"VPOptions"; if ( bsSuffix ) bsCode += bsSuffix; break; default: break; } *Code = bsCode.copy(); return S_OK; }
The following example illustrates the output produced by calling the code factory methods for a database verification point on a fully specified database verification point object.
In this example, the caller performed these preliminary steps:
DatabaseVP
object and set its VPname
property to Simple.
1.
DefineVP()
method.
This is consistent with the behavior you see with the QualityArchitect code generator. In the QueryBuilder invoked by the DefineVP()
method, the caller specified an ODBC data source called COFFEEBREAK
, and built the SQL statement "SELECT * FROM COFFEES"
.
The following samples show what the code factory methods return when they are invoked for this database verification point object:
CodeFactoryGetConstructorInvocation()
:
Dim Simple1 As New DatabaseVP
CodeFactoryGetNumExternalizedInputs()
:
3
CodeFactoryGetExternalizedInputDecl()
:
Dim VPname1 As String Dim VPConnectString1 As String Dim VPSQL1 As String
CodeFactoryGetExternalizedInputInit()
:
VPname1 = "Simple" VPConnectString1= "Provider=MSDASQL.1;Persist Security Info=False; Data Source=COFFEEBREAK" VPSQL1 = "select * from coffees"
CodeFactoryGetPropertySet()
:
Simple1.VPname = VPname1 Simple1.ConnectionString = VPConnectString1 Simple1.SQL = VPSQL1
Several flags are predefined for all verification points (see the property table in Summary), but you can add additional flags for your new verification point type. If you do so, use a single bit to represent each option, with the first available bit being the fourth bit (0x8
).
The Options
property is defined in the IVerificationPoint
interface as a Variant
, but it is implemented as a Long
bitfield.
The following example shows a mechanism for creating new options:
STDMETHODIMP CDatabaseVP::get_Options(/*[out, retval]*/ VARIANT *pVal) { pVal->vt = VT_I4; pVal->lVal = lOptions; return S_OK; } STDMETHODIMP CDatabaseVP::put_Options(/*[in]*/ VARIANT newVal) { switch (newVal.vt) { case VT_I4: lOptions = newVal.lVal; break; case VT_I2: lOptions = (LONG) newVal.iVal; break; case VT_UI4: lOptions = (LONG) newVal.ulVal; break; case VT_UI2: lOptions = (LONG) newVal.uiVal; break; case VT_UINT: lOptions = (LONG) newVal.uintVal; break; case VT_INT: lOptions = (LONG) newVal.intVal; break; default: return S_FALSE; } return S_OK; }
Your verification point component's interface contains properties or methods for defining the verification point's metadata-- for example:
STDMETHODIMP CDatabaseVP::get_ConnectionString(BSTR *pVal) { if (pVal == NULL) return E_POINTER; *pVal = SysAllocString(bstConnectionString); return S_OK; } STDMETHODIMP CDatabaseVP::put_ConnectionString(BSTR newVal) { bstConnectionString = newVal; CalcIsDefined(); return S_OK; } STDMETHODIMP CDatabaseVP::get_SQL(BSTR *pVal) { if (pVal == NULL) return E_POINTER; *pVal = SysAllocString(bstSQL); return S_OK; } STDMETHODIMP CDatabaseVP::put_SQL(BSTR newVal) { bstSQL = newVal; CalcIsDefined(); return S_OK; }
The framework uses the following two IPersistFile
interface methods to serialize your verification point's metadata. The other IPersistFile
methods can simply return E_NOTIMPL
:
Load()
. loads your verification point's metadata from a verification point metafile (.vpm
file). The framework calls this method if a test script calls the PerformTest()
method when the verification point is not yet fully defined (that is, when one or more required pieces of metadata are missing).
Save()
. saves your verification point's metadata to a .vpm
metafile. The framework calls this method when both of the following conditions exist:
PerformTest()
method is called when the verification point is not yet fully defined.
When these conditions exist, the framework first calls the DefineVP()
method to prompt the tester for the metadata, and then calls Save()
to store the metadata for future runs of this verification point.
The framework also calls Save()
to write a copy of the metafile to the Log folder for use by the Grid Comparator.
The following example illustrates metadata serialization:
STDMETHODIMP CDatabaseVP::GetClassID(CLSID *pClassID) { return E_NOIMPL; } STDMETHODIMP CDatabaseVP::IsDirty() { return E_NOIMPL; } STDMETHODIMP CDatabaseVP::SaveCompleted(LPCOLESTR pszFileName) { return E_NOIMPL; } STDMETHODIMP CDatabaseVP::GetCurFile(LPOLESTR *ppszFileName) { return E_NOIMPL; } STDMETHODIMP CDatabaseVP::Load(LPCOLESTR pszFileName, DWORD dwMode) { TCHAR szBuffer[4096]; _bstr_t bstFile(pszFileName); long lReadOptions = 0; GetPrivateProfileString(_T("Definition"), _T("Case ID"), _T("\n"), szBuffer, 4096, (LPCTSTR) bstFile ); // Verify that the file exists and that there is at least a VP name. if ( _tcscmp(szBuffer, _T("\n")) == 0 ) return E_VP_FILENOTFOUND; else put_VPname(_bstr_t(szBuffer)); GetPrivateProfileString(_T("Definition"), _T( "Verification Method"), _T("\n"), szBuffer, 4096, (LPCTSTR) bstFile ); // Check to see if comparison is Case Insensitive if ( _tcscmp(szBuffer, _T("CaseInsensitive")) == 0 ) lReadOptions |= VPOPTION_COMPARE_CASEINSENSITIVE; GetPrivateProfileString(_T("Definition"), _T("Expected Result"), _T("\n"), szBuffer, 4096, (LPCTSTR) bstFile ); // Check to see if expected result is failure if ( _tcscmp(szBuffer, _T("Failure")) == 0 ) lReadOptions |= VPOPTION_EXPECT_FAILURE; GetPrivateProfileString(_T("DatabaseVP"), _T("Connection String"), _T("\n"), szBuffer, 4096, (LPCTSTR) bstFile ); // Connection String is a required field. if ( _tcscmp(szBuffer, _T("\n")) == 0 ) return E_VP_BADFILE; else put_ConnectionString(_bstr_t(szBuffer)); GetPrivateProfileString(_T("DatabaseVP"), _T("SQL"), _T("\n"), szBuffer, 4096, (LPCTSTR) bstFile ); // SQL is a required field. if ( _tcscmp(szBuffer, _T("\n")) == 0 ) return E_VP_BADFILE; else put_SQL(_bstr_t(szBuffer)); GetPrivateProfileString(_T("DatabaseVP"), _T("Trim Whitespace"), _T("\n"), szBuffer, 4096, (LPCTSTR) bstFile ); // Check to see if we should trim whitespace if ( _tcscmp(szBuffer, _T("1")) == 0 ) lReadOptions |= DATABASEVPOPTION_TRIM_WHITESPACE; lOptions = lReadOptions; CalcIsDefined(); return S_OK; } STDMETHODIMP CDatabaseVP::Save(LPCOLESTR pszFileName, BOOL fRemember) { BSTR bsName = NULL; get_VPname( &bsName ); _bstr_t bstName(bsName); _bstr_t bstFile(pszFileName); // Write [Definition] section WritePrivateProfileString(_T("Definition"), NULL, NULL, (LPCTSTR)bstFile); WritePrivateProfileString(_T("Definition"), _T("Case ID"), (LPCTSTR)bstName, (LPCTSTR)bstFile); WritePrivateProfileString(_T("Definition"), _T("Type"), _T("Object Data"), (LPCTSTR)bstFile); WritePrivateProfileString(_T("Definition"), _T("Data Test"), _T("Contents"), (LPCTSTR)bstFile); if ( lOptions & VPOPTION_COMPARE_CASEINSENSITIVE ) WritePrivateProfileString(_T("Definition"), _T("Verification Method"), _T("CaseInsensitive"), (LPCTSTR)bstFile); else WritePrivateProfileString(_T("Definition"), _T("Verification Method"), _T("CaseSensitive"), (LPCTSTR)bstFile); if ( lOptions & VPOPTION_EXPECT_FAILURE ) WritePrivateProfileString(_T("Definition"), _T("Expected Result"), _T("Failure"), (LPCTSTR)bstFile); // Write [DatabaseVP] section WritePrivateProfileString(_T("DatabaseVP"), NULL, NULL, (LPCTSTR)bstFile); WritePrivateProfileString(_T("DatabaseVP"), _T("Connection String"), (LPCTSTR)bstConnectionString, (LPCTSTR)bstFile); WritePrivateProfileString(_T("DatabaseVP"), _T("SQL"), (LPCTSTR)bstSQL, (LPCTSTR)bstFile); if ( lOptions & DATABASEVPOPTION_TRIM_WHITESPACE ) WritePrivateProfileString(_T("DatabaseVP"), _T("Trim Whitespace"), _T("1"), (LPCTSTR)bstFile); return S_OK; }
IVPFramework
is the base class for IVerificationPoint
, and IVerificationPoint
is the base class for your verification point's interface. Consequently, you must provide entry points for all of the methods in the IVPFramework
interface.
Because the implementation of the IVPFramework
methods is provided in the VPFramework
component, your class must simply construct a VPFramework
object, and then pass each of the IVPFramework
methods to that VPFramework
object.
This form of implementation inheritance is called containment, and it is illustrated in the following example:
STDMETHODIMP CDatabaseVP::get_VPname(BSTR *pVal) { return m_pFramework->get_VPname(pVal); } STDMETHODIMP CDatabaseVP::put_VPname(BSTR newVal) { return m_pFramework->put_VPname(newVal); } STDMETHODIMP CDatabaseVP::get_VP( IDispatch ** ppVP ) { return m_pFramework->get_VP(ppVP); } STDMETHODIMP CDatabaseVP::put_VP( IDispatch * pVP ) { return m_pFramework->put_VP(pVP); } STDMETHODIMP CDatabaseVP::get_Plumbing( IDispatch ** ppPlumbing ) { return m_pFramework->get_Plumbing(ppPlumbing); } STDMETHODIMP CDatabaseVP::put_Plumbing( IDispatch * pPlumbing ) { return m_pFramework->put_Plumbing(pPlumbing); } STDMETHODIMP CDatabaseVP::get_CodeFactorySuffix(BSTR *pVal) { return m_pFramework->get_CodeFactorySuffix(pVal); } STDMETHODIMP CDatabaseVP::put_CodeFactorySuffix(BSTR newVal) { return m_pFramework->put_CodeFactorySuffix(newVal); } STDMETHODIMP CDatabaseVP::PerformTest(/*[in]*/ VARIANT Object, /*[optional, in]*/ VARIANT ExpectedData, /*[optional, in] */ VARIANT ActualData, /*[out, retval]*/ enum VPResult *Result) { return m_pFramework->PerformTest(Object, ExpectedData, ActualData, Result); }
In addition to the implementation tasks already described in this section, your verification point component must also:
These tasks are described in the following subsections:
You create a FinalConstruct()
method to initialize your verification point objects. The FinalConstruct()
method must be defined with a DECLARE_PROTECT_FINAL_CONSTRUCT()
statement in the class header file, as illustrated in the example in the section The Verification Point Component.
The FinalConstruct()
method must perform the following tasks:
VPFramework
object that your verification point contains.
VPFramework
object.
VPPlumbing
class with a ProgID
for each component in your verification point.
The following is an example of a FinalConstruct()
method:
HRESULT CDatabaseVP::FinalConstruct() { HRESULT hrRetVal = m_pFramework.CoCreateInstance(L"RTComVP.VPFramework"); CComQIPtr<IVPPlumbing, &IID_IVPPlumbing> plumbing; LPDISPATCH pTemp; _bstr_t bsComparator(L"RTComVP.DatabaseVPComparator"); _bstr_t bsData(L"RTComVP.DatabaseVPData"); _bstr_t bsDataProvider(L"RTComVP.DatabaseVPDataProvider"); _bstr_t bsDataRenderer(L"RTComVP.DatabaseVPDataRenderer"); m_pFramework->get_Plumbing(&pTemp); plumbing = pTemp; m_pFramework->put_VP(this); plumbing->InitializeFramework(bsComparator, bsData, bsDataProvider, bsDataRenderer); lOptions = 0; bstConnectionString = ""; bstSQL = ""; _com_error e(hrRetVal); _bstr_t bsError = e.ErrorMessage(); return S_OK; }
The VPPlumbing
class contains the Boolean property IsDefined
. The framework uses this property to determine if a verification point's metadata is fully specified when PerformTest()
is invoked. If IsDefined
is set to VARIANT_FALSE
, the framework calls DefineVP()
to prompt the tester for the missing metadata.
Your verification point implementation is responsible for coordinating the value of this property with the state of the metadata in your verification point object. The Load()
method and the property-set methods should update the IsDefined
value if they result in a change in the verification point's definition (that is, the verification point's metadata becomes fully specified or becomes no longer fully specified).
Note that the DatabaseVP
component implements a private method named CalcIsDefined()
. This method determines the state of the verification point's metadata and sets the IsDefined
flag accordingly. All methods that might change the state of a verification point's metadata can invoke CalcIsDefined()
.
Here is an example of IsDefined
flag maintenance:
HRESULT CDatabaseVP::CalcIsDefined() { CComQIPtr<IVPPlumbing, &IID_IVPPlumbing> plumbing; LPDISPATCH pTemp; m_pFramework->get_Plumbing(&pTemp); plumbing = pTemp; BSTR bsName; get_VPname(&bsName); _bstr_t bstName(bsName); BSTR bsConn; get_ConnectionString(&bsConn); _bstr_t bstConnectionString(bsConn); BSTR bsSQL; get_SQL(&bsSQL); _bstr_t bstSQL(bsSQL); if ( bstName.length() != 0 && bstConnectionString.length() != 0 && bstSQL.length() != 0 ) plumbing->put_IsDefined(VARIANT_TRUE); else plumbing->put_IsDefined(VARIANT_FALSE); return S_OK; }
You must define an interface for your verification point data component. This interface must inherit from IVerificationPointData
.
Your verification point data component that implements this interface contains a snapshot of the data being verified. That data can be either expected data or actual data.
The test designer should be able to use this interface to populate a Verification Point Data component for use with dynamic or manual verification points (for information, see Types of Verification Points).
The following is an example of an implementation of your verification point data component:
[ object, uuid(7C4870B3-6E1A-11D4-9A26-0010A4E86989), dual, helpstring("IDatabaseVPData Interface"), pointer_default(unique) ] interface IDatabaseVPData : IVerificationPointData { [propget, helpstring("property NumCols")] HRESULT NumCols( [out, retval] long *pVal); [propput, helpstring("property NumCols")] HRESULT NumCols( [in] long newVal); [propget, helpstring("property NumRows")] HRESULT NumRows( [out, retval] long *pVal); [propput, helpstring("property NumRows")] HRESULT NumRows( [in] long newVal); [propget, helpstring("property Columns")] HRESULT Columns( [out, retval] VARIANT *pVal); [propput, helpstring("property Columns")] HRESULT Columns( [in] VARIANT newVal); [propget, helpstring("property Row")] HRESULT Row([in] long Index, [out, retval] VARIANT *pVal); [propput, helpstring("property Row")] HRESULT Row([in] long Index, [in] VARIANT newVal); };
Your verification point data component implements the methods defined in your verification point data interface -- for example:
STDMETHODIMP CDatabaseVPData::get_NumCols(long *pVal) { *pVal = lCols; return S_OK; } STDMETHODIMP CDatabaseVPData::put_NumCols(long newVal) { lCols = newVal; return S_OK; } STDMETHODIMP CDatabaseVPData::get_NumRows(long *pVal) { *pVal = lRows; return S_OK; } STDMETHODIMP CDatabaseVPData::put_NumRows(long newVal) { lRows = newVal; return S_OK; } STDMETHODIMP CDatabaseVPData::get_Columns(VARIANT *pVal) { VariantInit(pVal); pVal->vt = (VT_ARRAY | VT_BYREF | VT_BSTR); ... // Copy the array and return it to the caller. ... return S_OK; } STDMETHODIMP CDatabaseVPData::put_Columns(VARIANT newVal) { pvColumns = new VARIANT; VariantInit(pvColumns); pvColumns->vt = (VT_ARRAY | VT_BSTR); SafeArrayCopy(newVal.parray, &(pvColumns->parray)); put_NumCols(newVal.parray->rgsabound[0].cElements); return S_OK; } STDMETHODIMP CDatabaseVPData::get_Row(long Index, VARIANT *pVal) { VariantInit(pVal); pVal->vt = (VT_ARRAY | VT_BYREF | VT_BSTR); if ( paRows != NULL ) { ... // Copy the row into the output array ... } return S_OK; } STDMETHODIMP CDatabaseVPData::put_Row(long Index, VARIANT newVal) { ... // Copy this row into our data structure ... return S_OK; }
In addition, your verification point data component must implement:
IPersistFile
interface to provide for its own serialization.
FileExtension()
property methods.
The following sections describe these tasks.
Your verification point class must implement its own serialization to a verification point data file by implementing the IPersistFile
interface.
As with your verification point class, you only need to implement the Load()
and Save()
methods, and you may return E_NOTIMPL
for the other methods.
Because the Grid Comparator in the current version of the product also accesses your metadata and data files, you must use a .ini
metafile format and a .csv
data file format. In a future release, you will be able to create your own comparator applications to read from and write to files of any data file format you choose.
The following is an example of data file serialization:
STDMETHODIMP CDatabaseVPData::GetClassID(CLSID *pClassID) { return E_NOTIMPL; } STDMETHODIMP CDatabaseVPData::IsDirty() { return E_NOTIMPL ; } STDMETHODIMP CDatabaseVPData::SaveCompleted(LPCOLESTR pszFileName) { return E_NOTIMPL ; } STDMETHODIMP CDatabaseVPData::GetCurFile(LPOLESTR *ppszFileName) { return E_NOTIMPL; } STDMETHODIMP CDatabaseVPData::Load(LPCOLESTR pszFileName, DWORD dwMode) { SAFEARRAY *psaColumns = NULL; SAFEARRAY *psaRow = NULL; wstring buffer; HRESULT hr = S_OK; wchar_t delim = L'\n'; long lNumVals = 0; _bstr_t bsFile = pszFileName; wifstream stream(bsFile); getline(stream, buffer, delim); if ( buffer.empty() != true ) { lNumVals = GetNumElementsFromCSVString( buffer.c_str() ); hr = BuildBSTRSafeArrayFromCSVString( buffer.c_str(), &psaColumns, lNumVals, 0 ); if ( hr == S_OK ) { pvColumns = new VARIANT; VariantInit(pvColumns); pvColumns->vt = (VT_ARRAY | VT_BSTR); pvColumns->parray = psaColumns; put_NumCols(lNumVals); } else { return E_INVALIDARG; } } else { put_NumCols(0); put_NumRows(0); return S_OK; } getline(stream, buffer, delim); for ( long l = 0; true != buffer.empty(); l++ ) { hr = BuildBSTRSafeArrayFromCSVString( buffer.c_str(), &psaRow, lNumVals, 0 ); if ( hr == S_OK ) { VARIANT vRow; VariantInit(&vRow); vRow.vt = (VT_ARRAY | VT_BSTR); vRow.parray = psaRow; put_Row(l, vRow); VariantClear( &vRow ); } else { return E_INVALIDARG; } getline(stream, buffer, delim); } return S_OK; } STDMETHODIMP CDatabaseVPData::Save(LPCOLESTR pszFileName, BOOL fRemember) { SAFEARRAY *psaColumns = NULL; SAFEARRAY *psaRow = NULL; FILE *pfOut = NULL; // Validate parameters if ( pszFileName == NULL ) return E_POINTER; if ( *pszFileName == L'\0' ) return E_INVALIDARG; if ( pvColumns == NULL || pvColumns->vt != (VT_ARRAY | VT_BSTR) ) return E_INVALIDARG; psaColumns = pvColumns->parray; // If there's nothing to write -- don't write anything... lCols = psaColumns->rgsabound[0].cElements; if ( lCols == 0 ) return S_OK; // Open the file pfOut = _wfopen(pszFileName, L"wt"); if ( pfOut == NULL ) return E_INVALIDARG; // Write out the file! // First print out a line with all the column names. WriteBSTRSafeArrayToCSVFile(pfOut, psaColumns); for ( long l=0; l < lRows; l++ ) { psaRow = paRows[l].parray; if ( psaRow != NULL) WriteBSTRSafeArrayToCSVFile(pfOut, psaRow); } fclose(pfOut); return S_OK; }
Your verification point data component must implement the following FileExtension()
property methods:
In the current release of QualityArchitect, set this property to csv
. In a future release, this property should contain the file extension associated with the data file format used by your verification point data component -- for example, csv
, dat
, or xml
.
The verification point framework creates a unique data file name and passes it to the Load()
and Save()
methods. The FileExtension()
property method tells the framework the file extension to use for this verification point data type.
Your specialized Verification Point Data Comparator component must implement the IVerificationPointComparator
interface. This interface has only one method, Compare()
.
The Compare()
method compares an expected data object and an actual data object, both of type IVerificationPointData
, and determines whether the test succeeds or fails.
The following is an example of a data comparison:
STDMETHODIMP CDatabaseVPComparator::Compare( /*[in]*/ IVerificationPointData *ExpectedData, /*[in]*/ IVerificationPointData *ActualData, /*[in]*/ VARIANT Options, /*[out]*/ BSTR *FailureDescription, /*[out, retval]*/ VARIANT_BOOL *Result) { CComQIPtr<IDatabaseVPData, &IID_IDatabaseVPData> vpdExpected; CComQIPtr<IDatabaseVPData, &IID_IDatabaseVPData> vpdActual; vpdExpected = ExpectedData; vpdActual = ActualData; *Result = VARIANT_FALSE; long lNumCols = 0; bool bCaseInsensative = false; BSTR bsFoo; // Allow for NULL FailureDescription if ( FailureDescription == NULL ) { // assign to throwaway local FailureDescription = &bsFoo; } // First compare the column names. VARIANT vExpColumns; VARIANT vActColumns; SAFEARRAY *psaExpColumns; SAFEARRAY *psaActColumns; vpdExpected->get_Columns(&vExpColumns); vpdActual->get_Columns(&vActColumns); if ( vExpColumns.vt == (VT_ARRAY | VT_BYREF | VT_BSTR) && vActColumns.vt == (VT_ARRAY | VT_BYREF | VT_BSTR)) { psaExpColumns = *(vExpColumns.pparray); psaActColumns = *(vActColumns.pparray); lNumCols = psaExpColumns->rgsabound[0].cElements; if ((unsigned long)lNumCols != psaActColumns->rgsabound[0].cElements) { *FailureDescription = SysAllocString( L"Expected and Actual resultsets had different number of columns."); return S_OK; } if ( !CompareBstrSafeArray(psaExpColumns, psaActColumns, NULL, bCaseInsensative)) { *FailureDescription = SysAllocString(L"Expected and Actual resultsets had different column names."); return S_OK; } } // Now loop over each of the adjacent rows and compare them. long lNumExpRows = 0; long lNumActRows = 0; vpdExpected->get_NumRows(&lNumExpRows); vpdActual->get_NumRows(&lNumActRows); if ( lNumExpRows != lNumActRows ) { *FailureDescription = SysAllocString(L"Expected and Actual resultsets had different number of rows."); return S_OK; } VARIANT vExpRow; VARIANT vActRow; SAFEARRAY *psaExpRow; SAFEARRAY *psaActRow; for ( long lIndex = 0; lIndex < lNumExpRows; lIndex++ ) { vpdExpected->get_Row(lIndex, &vExpRow); vpdActual->get_Row(lIndex, &vActRow); if ( vExpRow.vt == (VT_ARRAY | VT_BYREF | VT_BSTR) && vActRow.vt == (VT_ARRAY | VT_BYREF | VT_BSTR)) { psaExpRow = *(vExpRow.pparray); psaActRow = *(vActRow.pparray); if ( !CompareBstrSafeArray(psaExpRow, psaActRow, FailureDescription, bCaseInsensative)) { // FailureDescription filled in by CompareBstr rountine. return S_OK; } } else { ... // Problem with data objects -- handle error ... } } *Result = VARIANT_TRUE; return S_OK; }
Your specialized Verification Point Data Provider component must implement the IVerificationPointDataProvider
interface. This interface has only one method, CaptureData()
.
The CaptureData()
method reads the verification point's definition from the supplied verification point object, captures the data required by the verification point, and returns the data in a new IVerificationPointData
object.
The following example illustrates an implementation of the IVerificationPointDataProvider
interface:
STDMETHODIMP CDatabaseVPDataProvider::CaptureData ( /*[in]*/ VARIANT Object, /*[in]*/ IVerificationPoint *VP, /*[out, retval]*/ IVerificationPointData **Data) { // QI for DatabaseVP interface ... dbVP->get_ConnectionString(&bsConnection); _bstr_t bstConnection(bsConnection, false); dbVP->get_SQL(&bsSQL); _bstr_t bstSQL(bsSQL, false); // Attempt to connect to the OLE DB using the connection string // stored in the VP object. ... long lNumCols = rs->Fields->Count; if ( lNumCols > 0 ) { CComQIPtr<IDatabaseVPData, &IID_IDatabaseVPData> dataReturn; _bstr_t bstDataProgID = "rtComVP.DatabaseVPData"; dataReturn.CoCreateInstance(bstDataProgID); VARIANT v; v.vt = VT_I4; SAFEARRAYBOUND rgsaBound[1]; rgsaBound[0].lLbound = 0; rgsaBound[0].cElements = lNumCols; SAFEARRAY *psaColumns = SafeArrayCreate( VT_BSTR, 1, rgsaBound ); for ( long l=0; l < lNumCols; l++ ) { v.lVal = l; BSTR bsColumn = SysAllocString(rs->Fields->Item[v]->Name); SafeArrayPutElement( psaColumns, &l, bsColumn); } VARIANT vColumns; VariantInit(&vColumns); vColumns.vt = (VT_ARRAY | VT_BSTR); vColumns.parray = psaColumns; dataReturn->put_Columns(vColumns); rs->MoveLast(); long lNumRows = rs->RecordCount; rs->MoveFirst(); dataReturn->put_NumRows(lNumRows); for ( l=0; !rs->EOF; l++,rs->MoveNext() ) { SAFEARRAY *psaRow = SafeArrayCreate( VT_BSTR, 1, rgsaBound ); for ( long j = 0; j < lNumCols; j++ ) { v.lVal = j; _bstr_t Temp = rs->Fields->Item[v]->Value; BSTR bsData = SysAllocString(Temp); SafeArrayPutElement( psaRow, &j, bsData ); } VARIANT vRow; VariantInit(&vRow); vRow.vt = (VT_ARRAY | VT_BSTR); vRow.parray = psaRow; dataReturn->put_Row(l, vRow); } dataReturn->QueryInterface(IID_IVerificationPointData, (void **) Data); (*Data)->AddRef(); } // Clean up memory ... return S_OK; }
Your specialized Verification Point Data Renderer component must implement the IVerificationPointDataRenderer
interface. This interface has only one method, DisplayAndValidateData()
.
DisplayAndValidateData()
displays the data in an IVerificationPointData
object, allowing the tester to accept or reject that data as being correct.
The framework calls this method when both of the following conditions exist:
Options
property of the IVerificationPoint
component.
If the tester accepts the displayed data, the data is stored as the expected data for the static verification point. If the tester rejects the data, no expected data is stored, and the process is repeated the next time the verification point is executed.
The following example illustrates an implementation of the IVerificationPointDataRenderer
interface:
STDMETHODIMP CDatabaseVPDataRenderer::DisplayAndValidateData( /*[in, out]*/ IVerificationPointData **Data, /*[out, retval]*/ VARIANT_BOOL *Valid) { CComQIPtr<_clsDatabaseVPDataRenderer, &IID__clsDatabaseVPDataRenderer> pRend; CComQIPtr<IDatabaseVPData, &IID_IDatabaseVPData> pDBdata; VARIANT vCols; VARIANT vRow; VARIANT vAccepted; // Create an instance of the GUI data renderer. pRend.CoCreateInstance(L"rtCOMVpGui.clsDatabaseVPDataRenderer"); // Get a DatabaseVPData COM pointer. pDBdata = *Data; if ( pDBdata == NULL || pRend == NULL) { return S_FALSE; } // Put the columns from the data object into the GUI. pDBdata->get_Columns(&vCols); pRend->put_Columns(vCols); long lNumRows; pDBdata->get_NumRows(&lNumRows); // Put the rows from the data object into the GUI. for ( long l = 0; l < lNumRows; l++ ) { pDBdata->get_Row( l, &vRow ); pRend->put_Row( vRow ); } // Invoke the GUI dialog. pRend->DoModal(); // Pass back the result. pRend->get_Accepted(&vAccepted); if ( vAccepted.vt == VT_BOOL && vAccepted.boolVal == VARIANT_TRUE ) { *Valid = VARIANT_TRUE; } else { *Valid = VARIANT_FALSE; } return S_OK; }
Rational Test Script Services for Visual Basic | Rational Software Corporation |
Copyright (c) 2003, Rational Software Corporation | http://www.rational.com support@rational.com info@rational.com |