Implementing a New Verification Point |
To implement a new verification point, you must implement the following classes:
The following sections describe these classes.
Your specialized Verification Point class must extend the com.rational.test.vp.VerificationPoint
abstract class and implement all the abstract methods within it. For example, if you are implementing a verification point DatabaseVP
, use the following code:
public class DatabaseVP extends com.rational.test.vp.VerificationPoint
Further, your Verification Point class inherits the framework's entire behavior from this abstract base class. For details about this inherited behavior, see Integrating a Verification Point with QualityArchitect.
Your specialized Verification Point class must perform the following tasks:
Your verification point must contain member variables and corresponding get
/set
methods for all attributes necessary to describe the verification point's metadata.
The following example illustrates the use of get
/set
methods for retrieving and assigning metadata such as a JDBC user ID, password, url, and a SQL statement:
private String sSQL = ""; private String sJDBCuser = ""; private String sJDBCpassword = ""; private String sJDBCdriver = ""; private String sJDBCurl = ""; public String getSQL() { return sSQL; } public String getJDBCuser() { return sJDBCuser; } public String getJDBCpassword() { return sJDBCpassword; } public String getJDBCdriver() { return sJDBCdriver; } public String getJDBCurl() { return sJDBCurl; } public void setSQL( String sSQL ) { this.sSQL = sSQL; } public void setJDBCuser( String sJDBCuser ) { this.sJDBCuser = sJDBCuser; } public void setJDBCpassword( String sJDBCpassword ) { this.sJDBCpassword = sJDBCpassword; } public void setJDBCdriver( String sJDBCdriver ) { this.sJDBCdriver = sJDBCdriver; } public void setJDBCurl( String sJDBCurl ) { this.sJDBCurl = sJDBCurl; }
If a test script executes your verification point, but the verification point's metadata is not completely defined in the datastore, the verification point must run a UI that prompts the tester for the missing metadata. Specifically, you must provide the following features:
defineVPcallback()
method. (This is an abstract method of the VerificationPoint
base class.)
The defineVPcallback()
method presents the tester with your UI that prompts for the metadata. When the metadata is retrieved, the method populates the verification point's member variables with the metadata values -- for example:
public boolean defineVPcallback() { // Invoke some UI and populate the class with the VP's definition. }
Implement at least two constructors that use the super
keyword to call the constructor of the VerificationPoint
base class, as follows:
One of the tasks that the code factory methods (described in the next two steps) perform is to output code that invokes this constructor. As a result, this is the constructor that appears in scripts generated by a QualityArchitect code generator.
Both constructors must pass class objects for the following classes that you have implemented:
The verification point framework can then create instances of these classes to store, serialize, capture, display, and compare the data on which your verification point operated. An example of creating instances of these classes to perform the above methods is shown as follows:
public DatabaseVP( String sVPname ) {super(sVPname, DatabaseVPData.class, DatabaseVPDataProvider.class, DatabaseVPDataRenderer.class, DatabaseVPComparator.class);
setIsDefined(false); } public DatabaseVP( String sVPname, String sSQL, String sJDBCuser, String sJDBCpassword, String sJDBCdriver, String sJDBCurl ) {super(sVPname, DatabaseVPData.class, DatabaseVPDataProvider.class, DatabaseVPDataRenderer.class, DatabaseVPComparator.class);
this.sSQL = sSQL; this.sJDBCuser = sJDBCuser; this.sJDBCpassword = sJDBCpassword; this.sJDBCdriver = sJDBCdriver; this.sJDBCurl = sJDBCurl; if ( sSQL != null && !sSQL.equals("") && sJDBCdriver != null && !sJDBCdriver.equals("") && sJDBCurl != null && !sJDBCurl.equals("")) { setIsDefined(true); } else { setIsDefined(false); } }
The code factory methods are similar in function to Java Beans in that both provide additional design-time behavior that is integrated with a Java development environment.
If a QualityArchitect user wants to insert your verification point into a generated test script, the QualityArchitect code generator takes the following actions:
defineVPcallback()
method for the newly created verification point object, presenting the tester with the UI you created to prompt for the verification point's metadata.
For information about how the code generators use the code factory methods, see Integrating a Verification Point with QualityArchitect.
To enable the code generators to insert an instance of your verification point into a test script, implement the following code factory methods:
codeFactory_getConstructorInvocation()
returns a string of Java code that calls the fully specified constructor of your verification point. Rather than hard-coding the metadata into the constructor call, you should externalize any variables that testers might want to supply with values from a datapool at test runtime.
codeFactory_getNumExternalizedInputs(),
called by the code generator, determines how many externalized input variables are present in the constructor call.
codeFactory_getExternalizedInputDecl(),
called by the code generator, retrieves each externalized metadata variable.
The code generators call the codeFactory_getPrefix()
and codeFactory_setPrefix()
methods; you are not required to call them. However, you must call codeFactory_getPrefix()
when constructing the externalized variables returned by the codeFactory_getConstructorInvocation()
and codeFactory_getExternalizedInputDecl()
methods.
If the code generators set a prefix, prepend the prefix to each externalized variable name used with the codeFactory_getConstructorInvocation()
and codeFactory_getExternalizedInputDecl()
methods. Doing so ensures that externalized variable names in different verification points within the same scope will be unique.
The following example illustrates the use of code factory methods:
public intcodeFactory_getNumExternalizedInputs()
{ int iLines = 0; // At least 6 lines of code, 4 for JDBC connect info, 1 for VP name and // 1 for SQL statement. iLines += 6; if ( getOptions() != 0 ) { // If the user set any options, need to add another variable for that. iLines++; } return iLines; } public StringcodeFactory_getExternalizedInputDecl( int nInput )
{ String sCode = ""; String sPrefix = this.codeFactory_getPrefix()
; // Out of range request gets an empty string (still valid code...) if ( nInput <codeFactory_getNumExternalizedInputs()
) { switch ( nInput ) { case 0: sCode = "String s" + sPrefix + "JDBCdriver = \"" + sJDBCdriver + "\";"; break; case 1: sCode = "String s" + sPrefix + "JDBCurl = \"" + sJDBCurl + "\";"; break; case 2: sCode = "String s" + sPrefix + "JDBCuser = \"" + sJDBCuser + "\";"; break; case 3: sCode = "String s" + sPrefix + "JDBCpassword = \"" + sJDBCpassword + "\";"; break; case 4: sCode = "String s" + sPrefix + "SQL = \"" + sSQL + "\";"; break; case 5: sCode = "String s" + sPrefix + "VPname = \"" + getVPname() + "\";"; break; case 6: sCode = "int i" + sPrefix + "Options = " + Integer.toString(getOptions()) + ";"; break; default: sCode = ""; break; } } return sCode; } public StringcodeFactory_getConstructorInvocation()
{ StringBuffer sCode = new StringBuffer(""); String sPrefix = this.codeFactory_getPrefix()
; sCode.append("DatabaseVP "); sCode.append(sPrefix); sCode.append(this.getVPname()); sCode.append(" = new DatabaseVP( \""); sCode.append(this.getVPname()); sCode.append("\", s"); sCode.append(sPrefix); sCode.append("SQL, s"); sCode.append(sPrefix); sCode.append("JDBCuser, s"); sCode.append(sPrefix); sCode.append("JDBCpassword, s"); sCode.append(sPrefix); sCode.append("JDBCdriver, s"); sCode.append(sPrefix); sCode.append("JDBCurl"); if ( this.getOptions() != 0 ) { sCode.append(", i"); sCode.append(sPrefix); sCode.append("Options);"); } else sCode.append(");"); return sCode.toString(); }
Implement readFile()
and writeFile()
methods to serialize verification point metadata.
The metadata file is read by both the Verification Point Data Comparator class and the TestManager comparator software. Currently, the only supported metadata file format is .ini
file format.
A future release of QualityArchitect will support custom-built comparators in addition to the TestManager comparator. As a result, you will be able to use any metadata (and data) file format that your custom comparator supports.
When reading and writing your metadata file, store all metadata for your verification point, as well as properties for the additional [Definition]
section in the .ini
file, as shown in the following example:
public void writeFile(OutputStream out)
throws IOException
{
// If there's nothing to write -- don't write anything...
if ( sJDBCdriver == "" || sJDBCurl == "" || sSQL == "" )
return;
PrintWriter pwOut = new PrintWriter ( new BufferedWriter (
new OutputStreamWriter ( out )));
// Write out the [Definition] section
pwOut.println("[Definition]");
// Write the VP name
pwOut.println("Case ID=" + this.getVPname());
// Write the VP type
pwOut.println("Type=Object Data");
// Write the data test
pwOut.println("Data Test=Contents");
// Write the verification method
if ( (getOptions() & COMPARE_CASEINSENSITIVE) != 0 )
pwOut.println("Verification Method=CaseInsensitive");
else
pwOut.println("Verification Method=CaseSensitive");
// Write out the DatabaseVP specific section.
pwOut.println("");
pwOut.println("[Database VP]");
// Write out the JDBC connect info
pwOut.println("JDBCdriver=" + sJDBCdriver);
pwOut.println("JDBCurl=" + sJDBCurl);
pwOut.println("JDBCuser=" + sJDBCuser);
pwOut.println("JDBCpassword=" + sJDBCpassword);
// Write out the Select statement
pwOut.println("SQL=" + sSQL);
// Flush the output, and close the file.
pwOut.flush();
}
public void readFile(InputStream in) throws IOException
{
try
{
Hashtable tblINI = CTutil.mapINIfile( in );
if ( tblINI != null )
{
String sDef = "Definition";
String sDBVP = "Database VP";
// Read out all the entries we care about.
String sVerMethod = CTutil.readPrivateProfileString(tblINI, sDef,
"Verification Method");
if ( sVerMethod.equals("CaseInsensitive") )
setOptions(getOptions()|COMPARE_CASEINSENSITIVE);
sJDBCdriver = CTutil.readPrivateProfileString(tblINI, sDBVP,
"JDBCdriver");
sJDBCurl = CTutil.readPrivateProfileString(tblINI, sDBVP,
"JDBCurl");
sJDBCuser = CTutil.readPrivateProfileString(tblINI, sDBVP,
"JDBCuser");
sJDBCpassword = CTutil.readPrivateProfileString(tblINI, sDBVP,
"JDBCpassword");
sSQL = CTutil.readPrivateProfileString(tblINI, sDBVP, "SQL");
}
}
catch ( IOException exc ) { }
return;
}
Your specialized Verification Point Data class must implement the com.rational.test.vp.VerificationPointData
interface and perform the following high-level tasks:
readFile()
and writeFile()
methods to serialize the data to a verification point data file.
getFileExtension()
method.
Create member variables that encapsulate the data that the verification point is comparing. The data encapsulated in these member variables should be exposed through public get
and set
methods that you implement. Doing so allows a test script to create and populate an instance of the class for use in dynamic and manual verification points.
The following example uses the public getData()
and setData()
methods to encapsulate the data objects being compared:
private String[] asColumns = null; private Vector vData = null; public int getNumCols() { if (asColumns != null ) return asColumns.length; else return 0; } public int getNumRows() { if ( vData != null ) return vData.size(); else return 0; } public String[] getColumns() { return asColumns; } public void setColumns( String[] asColumns ) { this.asColumns = asColumns; } public VectorgetData()
{ return vData; } public voidsetData( Vector vData )
{ this.vData = vData; }
Implement readFile()
and writeFile()
methods to serialize verification point data.
The data file is read by both the Verification Point Data Comparator class and the TestManager comparator software. Currently, the only supported data file format is .csv
file format.
A future release of QualityArchitect will support custom-built comparators in addition to the TestManager comparator. As a result, you will be able to use any data (and metadata) file format that your custom comparator supports.
The following example illustrates reading from and writing to a .csv
file:
public voidwriteFile(OutputStream out)
throws IOException { // If there's nothing to write -- don't write anything... if ( asColumns == null || vData == null || asColumns.length == 0 ) return; PrintWriter pwOut = new PrintWriter ( new BufferedWriter ( new OutputStreamWriter ( out ))); // First print out a line with all the column names. String csvColumns = ""; int numCols = getNumCols(); for ( int i=0; i < numCols; i++ ) { if ( i > 0 ) csvColumns = csvColumns + "," + "\"" + asColumns[i] + "\""; else csvColumns = "\"" + asColumns[i] + "\""; } pwOut.println(csvColumns); // Next print out a line for each element in our vector of data. int numRows = getNumRows(); for ( int i=0; i < numRows; i++ ) { Object obj = vData.elementAt(i); if ( obj != null ) { // Verify that obj is an array of strings String[] asData = (String[]) obj; if ( asData.length != numCols ) { // Don't write out this row, and write an error message // to the log about the format of this object. // Log warning message here. } else { String csvRow = ""; for ( int j=0; j < numCols; j++ ) { if ( j > 0 ) csvRow = csvRow + "," + "\"" + asData[j] + "\""; else csvRow = "\"" + asData[j] + "\""; } pwOut.println(csvRow); } } } // Flush the output. pwOut.flush(); } public voidreadFile(InputStream in)
throws IOException, ClassNotFoundException { BufferedReader brIn = new BufferedReader ( new InputStreamReader ( in )); // Read in the array of column names String sColumns = brIn.readLine(); // If the file is empty, we're done. if ( sColumns == null || sColumns.length() == 0 ) return; StringBuffer bufCSV = new StringBuffer(sColumns); StringBuffer bufElement = new StringBuffer(""); int numCols = 0; boolean bMore = true; Vector vColumns = new Vector(); while (bMore == true) { bMore = CTutil.csvGetNextElement(bufCSV, bufElement); String sElement = bufElement.toString(); // Remove quotes around string if they are present. if ( sElement.startsWith("\"") && sElement.endsWith("\"") ) { sElement = sElement.substring(1, sElement.length() - 1); } vColumns.addElement(sElement); numCols++; } // Turn the vector into an array of strings. asColumns = (String[])CTutil.toArray(vColumns, new String[1]); // Now read in all the data lines. String sData = ""; Vector vRow = new Vector(); vData = new Vector(); for ( sData = brIn.readLine(); sData != null; sData = brIn.readLine() ) { bufCSV = new StringBuffer(sData); bufElement.setLength(0); int numElements = 0; bMore = true; vRow.removeAllElements(); while (bMore == true) { bMore = CTutil.csvGetNextElement(bufCSV, bufElement); String sElement = bufElement.toString(); // Remove quotes around string if they are present. if ( sElement.startsWith("\"") && sElement.endsWith("\"") ) { sElement = sElement.substring(1, sElement.length() - 1); } vRow.addElement(sElement); numElements++; } if ( numElements == numCols ) { vData.addElement(CTutil.toArray(vRow, new String[1])); } else { // Handle the exception. } } }
Call getFileExtension()
to provide the extension of the data file to the test script.
In this release of QualityArchitect, this method always returns csv
. In a future release, the method will return the file extension used by whatever data file format (for example, .csv
, .dat
, .xml
) that you select for the data in your Verification Point Data class.
The verification point framework creates the unique file name and data file passed to the writeFile()
and readFile()
methods. The getFileExtension()
method tells the framework what file extension to use, as shown in the following example:
public String getFileExtension() { return "csv"; }
Your specialized Verification Point Data Comparator class must implement the com.rational.test.vp.VerificationPointDataComparator
interface.
The only method in this interface is compare()
. This method compares an expected data object with an actual data object (both of type VerificationPointData
) and determines whether the test passes or fails.
The following example illustrates a comparison of two data objects:
public boolean compare( VerificationPointData vpdExpected,
VerificationPointData vpdActual,
Object objOptions,
StringBuffer sFailureDescription )
{
boolean bIdentical = true;
StringBuffer bufActual = new StringBuffer();
StringBuffer bufExpected = new StringBuffer();
StringBuffer bufFailIndex = new StringBuffer();
Integer iOptions;
if ( objOptions != null )
iOptions = (Integer) objOptions;
else
iOptions = new Integer(0);
boolean bCaseInsensitive = (iOptions.intValue() &
VerificationPoint.COMPARE_CASEINSENSITIVE) != 0;
DatabaseVPData expected = (DatabaseVPData) vpdExpected;
DatabaseVPData actual = (DatabaseVPData) vpdActual;
if ( expected.getNumCols() != actual.getNumCols() )
{
String sText;
if ( expected.getNumCols() == 0 || actual.getNumCols() == 0 )
sText = "No column titles";
else
sText = "Differing number of columns";
sFailureDescription.insert(0, sText);
sFailureDescription.setLength(sText.length());
return false;
}
if ( expected.getNumRows() != actual.getNumRows() )
{
String sText = "Differing number of rows";
sFailureDescription.insert(0, sText);
sFailureDescription.setLength(sText.length());
return false;
}
if ( compareStringArray( expected.getColumns(), actual.getColumns(),
bCaseInsensitive, bufExpected, bufActual,
bufFailIndex) == false )
{
String sText = "Column title[" + bufFailIndex.toString() +
"]: expected[";
sText += bufExpected.toString() + "], actual[" +
bufActual.toString() + "].";
sFailureDescription.insert(0, sText);
sFailureDescription.setLength(sText.length());
return false;
}
// Walk the vectors of data and compare each row.
int numRows = expected.getNumRows();
int numCols = expected.getNumCols();
Vector vExpected = expected.getData();
Vector vActual = actual.getData();
String[] asExpected;
String[] asActual;
for ( int i=0; i < numRows; i++ )
{
Object obj = vExpected.elementAt(i);
asExpected = (String[]) obj;
obj = vActual.elementAt(i);
asActual = (String[]) obj;
if ( compareStringArray( asExpected, asActual, bCaseInsensitive,
bufExpected, bufActual, bufFailIndex ) == false )
{
// Row + 2 -> 1 for the column titles (which show up as a row)
// and one for 0 index into vector vs. 1 index in grid comparator.
String sText = "Difference found in row[" + Integer.toString(i+2);
sText += "], column[" + bufFailIndex.toString() + "].";
sFailureDescription.insert(0, sText);
sFailureDescription.setLength(sText.length());
return false;
}
}
return true;
}
private boolean compareStringArray( String[] asX, String[] asY,
boolean bCaseInsensitive, StringBuffer bufFailX,
StringBuffer bufFailY, StringBuffer bufFailIndex )
{
if ( asX.length != asY.length )
return false;
boolean bDifferent;
for ( int i=0; i < asX.length; i++ )
{
if ( bCaseInsensitive )
bDifferent = !asX[i].equalsIgnoreCase(asY[i]);
else
bDifferent = !asX[i].equals(asY[i]);
if ( bDifferent )
{
bufFailIndex.insert(0, Integer.toString(i+1));
bufFailIndex.setLength(Integer.toString(i).length());
bufFailX.insert(0, asX[i]);
bufFailX.setLength(asX[i].length());
bufFailY.insert(0, asY[i]);
bufFailY.setLength(asY[i].length());
return false;
}
}
return true;
}
Your specialized Verification Point Data Provider class must implement the com.rational.test.vp.VerificationPointDataProvider
interface.
The only method in this interface is captureData()
. This method uses the metadata in a VerificationPoint
object to construct and populate a VerificationPointData
object.
The following example illustrates an implementation of the captureData()
method:
public VerificationPointData captureData( java.lang.Object theObject,
VerificationPoint VP )
{
DatabaseVP theVP = (DatabaseVP) VP;
String sSQL = theVP.getSQL();
String sJDBCuser = theVP.getJDBCuser();
String sJDBCpassword = theVP.getJDBCpassword();
String sJDBCdriver = theVP.getJDBCdriver();
String sJDBCurl = theVP.getJDBCurl();
int iOptions = theVP.getOptions();
Connection con = theVP.getCon();
Statement stmt = theVP.getStmt();
DatabaseVPData vpsData = null;
// Capture the data!
if ( con == null || stmt == null )
{
// Create a JDBC connection and statement
try {
Class.forName(sJDBCdriver);
}
catch(ClassNotFoundException e) {
theVP.sFailureDescription =
"Database VP Error: Unable to load driver \""
+ sJDBCdriver + "\"";
theVP.bIsValid = false;
return vpsData;
}
try {
con = DriverManager.getConnection(sJDBCurl, sJDBCuser,
sJDBCpassword);
}
catch(SQLException ex) {
theVP.sFailureDescription =
"Database VP Error: Unable to Connect, UID = "
+ sJDBCuser + ", PWD = " + sJDBCpassword + ", URL = "
+ sJDBCurl + ", Error = " + ex.getMessage();
theVP.bIsValid = false;
return vpsData;
}
try {
stmt = con.createStatement();
}
catch(SQLException ex) {
theVP.sFailureDescription =
"Database VP Error: Unable to create Statement: "
+ ex.getMessage();
theVP.bIsValid = false;
return vpsData;
}
}
// Execute the query.
try {
ResultSet rs = stmt.executeQuery(sSQL);
ResultSetMetaData rsmd = rs.getMetaData();
vpsData = new DatabaseVPData();
int numColumns = rsmd.getColumnCount();
String[] asColumns = new String[numColumns];
// Build a String array of the Column Names
if ( (iOptions & DatabaseVP.OPTION_TRIM) != 0 )
{
for (int i=0; i < numColumns; i++)
{
asColumns[i] = rsmd.getColumnName(i+1).trim();
}
}
else
{
for (int i=0; i < numColumns; i++)
{
asColumns[i] = rsmd.getColumnName(i+1);
}
}
// Put the column data into the VPdata object
vpsData.setColumns(asColumns);
// Build a Vector of the data elements
Vector vData = new Vector();
int numRows = 0;
try {
while( rs.next() )
{
String[] asData = new String[numColumns];
if ( (iOptions & DatabaseVP.OPTION_TRIM) != 0 )
{
for (int j=0; j < numColumns; j++)
{
asData[j] = rs.getString(j+1).trim();
}
}
else
{
for (int j=0; j < numColumns; j++)
{
asData[j] = rs.getString(j+1);
}
}
// Put the array of strings into the vector at this row's
index.
vData.addElement((Object) asData);
numRows++;
}
}
catch(SQLException ex) {
theVP.sFailureDescription =
"Database VP Error: Unable to walk ResultSet. "
+ "Error = " + ex.getMessage();
theVP.bIsValid = false;
return null;
}
vpsData.setData(vData);
}
catch(SQLException ex) {
theVP.sFailureDescription =
"Database VP Error: Unable to execute Query \""
+ sSQL + "\", Error = " + ex.getMessage();
theVP.bIsValid = false;
return vpsData;
}
return vpsData;
}
Your specialized Verification Point Data Renderer class must implement the com.rational.test.vp.VerificationPointDataRenderer
interface.
The only method in this interface is displayAndValidateData()
. This method:
VerificationPointData
object
The verification point framework invokes displayAndValidateData()
when both of the following conditions apply:
OPTION_USER_ACKNOWLEDGE_BASELINE
option in the setOptions()
method of the specialized VerificationPoint
class.
When both of these conditions exist, the framework captures the baseline data object and then invokes displayAndValidateData()
to display the baseline data. The tester accepts or rejects the data:
In the following example, displayAndValidateData()
presents the baseline data object vpdData
to the tester for verification:
public boolean displayAndValidateData( VerificationPointData vpdData ) { // Pop up some UI which displays the vpdData object and prompts the // user to accept or reject. if ( bAccepted ) return true; else return false; }
Rational Test Script Services for Java | Rational Software Corporation |
Copyright (c) 2003, Rational Software Corporation | http://www.rational.com support@rational.com info@rational.com |