Real-world TestComplete Plug-ins Part IVa
Object Driven Testing (ODT) Class Declaration Generator
Welcome to the fourth installment in this series covering the TestComplete 4 plug-in SDK.
In the previous articles, we created the following plug-ins and examined associated parts of the SDK:
In this, the fourth article, we're examining the TestComplete SDK's extensive syntax system. Along with TestComplete's
support for 5 different scripting languages comes the need for a robust way to generate syntactically correct script code
for any of those languages. The syntax system provides a set of interfaces which we can use to have TestComplete generate
for us correct code in any supported language. We touched briefly on the syntax system in the previous article (Part III)
when we generated a call to a method of our plug-in. In Part IV, we go into much more detail. However, the scope of this
plug-in and the syntax system is so great, I've decided to split part IV into sub-parts. In this article, Part IVa, we'll
examine the plug-in itself, why you might need it and how to use it. In the next article, Part IVb, we'll dig into the
plug-in's implementation, and examine the syntax system interfaces directly. Thus, this article introduces the ODT Class
Declaration Generator plug-in and explains its use.
What is this plug-in?
The ODT Class Declaration Generator performs a rarified, though (for some people) rather vital function: it analyzes a
class that has been defined in the ODT visual panel, and then creates script code that can programmatically recreate that
class. A couple of years ago I created a library of script routines that performed the same function, but they were only
for DelphiScript (and then a later version for Jscript). Support for all 5 languages would have required creating and
maintaining the libraries in each language, not something for which I really had time. Also, the scripts were hard-coded
to their own language; thus DelphiScript would only generate DelphiScript.
The ODT Class Declaration Generator plug-in, on the other hand, supports all 5 TestComplete scripting languages,
using the TestComplete SDK syntax system to generate language-specific code. The plug-in also offers this handy feature:
you can generate a class declaration from one language into any other. Thus, if you have, for instance, a VBScript project
in which you've declared a useful ODT class and you wish to make use of that same class in a Jscript project, you can process
the class declaration in the VBScript project and specify that the plug-in generate Jscript code as output, which you could
then run in your Jscript project to re-create the ODT class there.
For example, if we were to process the clsFontTest class declaration in the TestComplete sample project, Simple ODT.pjs
(see figure 1) and generate it into Jscript, we'd end up with the code in figure 2.
Figure 1 - the clsFontTest class in the ODT panel
Figure 2 - clsFontTest declared in code.
For those not familiar with the programmatic side of the ODT engine, I'll explain this generated code.
The ODT engine has two sides: a visual interface and a programmatic (or run-time) interface. Here, in code, we're
recreating what we can see in the panel. First, we ask the ODT engine's Classes collection for a reference to the
clsFontTest class. If it already exists, we tell the ODT engine to delete that declaration in favor of our new declaration.
Note here the careful distinction that, in the code above, the lClass variable has a reference not to the clsFontTest class
itself, but to an object which represents the class declaration. In the TestComplete help, you'll find that this object is
of type "Class". Yes, it's the "Class" class. With that in mind, we're calling the AddProperty method of the Class object for
each of the properties declared in clsFontTest. Likewise, we then call Class.AddMethod for each method declared.
Aside from the possible confusion stemming from a class called "Class" (there are also classes called "Property" and
"Method", beware) this should be fairly straightforward: we declare the class, we add properties and we add methods. Of
course, ODT classes are rarely this simple, so this plug-in can handle complex properties including nested arrays, behold:
Figure 3 - the ODTGenTestClass class in the ODT panel
In the sample TestComplete project suite that comes with this article, I've declared a fairly complex, multi-level
class for testing purposes. Most the properties have default values and there are class-type properties and nested arrays.
The generator plug-in can handle this too. Here's the Jscript output:
Figure 4 - the generated Jscript.
You'll notice that the calls to AddProperty include both the name of the property and the default value that was
declared in the ODT panel. I won't explain all this generated code; it's covered in the TestComplete help; suffice it
to say that we can generate a script routine, in any script language, that can recreate an ODT class in the ODT engine.
Why do we need this plug-in?
I really dig the ODT features in TestComplete. When I was still with AutomatedQA I would speak incessantly about its
virtues, and later, when I was using TestComplete full-time, I put the ODT engine through some heavy use. It was then
that I discovered some of the weaknesses of the TestComplete ODT engine. Two of the most noteworthy are these:
- Limitations of the visual designer.
- Versioning issues can crop up quickly when ODT class declarations are being shared between multiple test developers and multiple test projects.
The first issue has several facets:
- The visual designer is not a two-way editor. The ODT class declarations are persisted in a proprietary indexed INI format rather than in a code-based declaration. This becomes a real problem if you use optimistic (non-locking) source control. There's no hope of merging the indexed INI file if two developers have edited the file simultaneously.
- Re-ordering properties or methods declared in the panel is not possible.
- Usability of the ODT panel is affected by the need for several single-click actions to enter values or script procedure names.
The second issue (versioning) became a real killer as we realized that various projects attempting to use the same ODT classes
(really just copies of a class) would often get their declarations out of synch and would cause strange results when test projects were played back.
Don't get me wrong, I still think that ODT is one of the best ways to make robust and flexible testing with TestComplete possible,
but I needed solutions to these issues or I wasn't going to be able to really get the most out of ODT. What I needed was a true
single-source solution for sharing ODT objects; the kind of solution I could only get through a code-based declaration of classes.
A code-based declaration of each ODT class would provide:
- A source-controlled, authoritative declaration of a class. No concerns over whether a project had its ODT class definitions up-to-date since the code-based declarations would simply over-write any declarations that exist in the ODT panel.
- An easy way to re-order properties and methods. Class members exist in created order; if you change the order in which they're added to the ODT class, you can change the order they appear in the ODT panel.
- A single source file which contains both the declaration of the ODT class and the class' implementation methods. This file can be easily shared among test projects and checked into source control systems.
How do we use this plug-in?
First let me say, I've probably made this plug-in a little more complex than necessary, but it is in part because I'm planning to
extend the implementation quite a bit, and decided to leave room to do so. As a result, you may see some places where use of the
plug-in could have been simplified; an example is the Template Strings in the options page (explanation to follow) - once it has
some more strings in it, it will make more sense, but at the moment it's a little weighty.
Now let's examine the two most common scenarios where you would use this plug-in:
- You have an ODT class that you created in the visual ODT panel, and you want to create a programmatic declaration (i.e. a declaration in code).
- You want to share an ODT between two different TestComplete projects that are in different scripting languages.
In the first scenario, we want to turn the plug-in loose on an ODT class that has been defined in the ODT panel,
generating script code in the same language as the project which currently contains the class. Right away we run into a problem:
I wanted this plug-in to basically be a 'design-time' plug-in, capable of just getting the class information from the ODT panel
and generating the script code from a button click of the design-time plug-in. Unfortunately, since the ODT engine and panel
themselves are plug-ins, and because we can only interact with another plug-in at run-time, we have to actually run the scripting
engine, and execute a run-time method of our plug-in in order to retrieve and process the desired ODT class. For that same reason,
we can't just pick the ODT class we want from a list, rather we need to supply to the run-time method the string name of the class
we wish to process. In other words, to process our ODT class and to generate the script code, we have to run a script method in
TestComplete to kick all this off. Fortunately, we have just such a method in the GenerateDeclaration() method. Really, that's all
you need to know, but now I'll ramble on about options and such for a bit…
After installing the plug-in, you'll find in the TestComplete code completion window the ODTDeclarationGenerator object, which has the following method:
If you call this method, supplying the name of an existing ODT class, the generated declaration will be returned by
the method call and also placed on the Windows clipboard.
For example, if you look in the DelphiScript project of the sample project suite, tsODTGen_SampleProjectSuite.pjs
at the Main unit's Main method, you'll find the following call:
Sending the result of the GenerateDeclaration call to the Log is a bit redundant, but it's done so that you can see the
results right away. In this case, there are two messages in the Log. The first is:
Class ODTGenTestClass has been processed and the resulting code placed on the Windows clipboard.
This is the message you'll normally see in the log when the class declaration has been generated successfully.
The next message is the generated script code (in DelphiScript):
You'll notice that there are actually two methods here, and that there are comments in the generated code.
There are two methods because ultimately I wish to generate four methods. One for the class declaration, one
as a convenient remove method (these two exist now), one as a "get" method - a sort of constructor, as it were,
and finally one as a validation method. I hope to have these last two methods added to the plug-in by the time
I write the second article - the one discussing the source code behind this plug-in.
As for the comments, they are actually customizable in the plug-in's options panel…
The Plug-in's Options Panel
If you examine the options panel for the ODT Generator plug-in, you'll see something like the following:
Figure 5 - the ODT Declaration Generator plug-in options
These options all have to do with what code is generated by the plug-in.
Function Comment - this specifies the comment string to appear before the generated declaration method. This string can make use of template strings (see below).
"Begin property declarations" Comment - specifies the comment that appears before the section of code that defines the ODT class' properties. Makes use of template strings.
"Find and delete" Comment - specifies the comment that appears before the code section that finds and deletes the old ODT class. Makes use of template strings.
"Begin method declaration" Comment - specifies the comment that appears before the section of code that defines the ODT class' methods. Makes use of template strings.
Function Name - this is the generated method's name. Also makes use of template strings.
Template Strings - this is a read-only table property containing a list of strings which will be replaced at code generation-time by the generated values. If you add one of the template strings to your comment properties, that template string will be replaced by the appropriate value at runtime. Thus, if your Function Name option is "DefineClass_%ODTCLASSNAME%" and you generate a declaration for "MyODTClass", the resulting declaration method would be named "DefineClass_MyODTClass".
Generate "Remove" Method - this property specifies whether the plug-in should generate the second method you saw, the "Remove_" method.
With the GenerateDeclaration method and these options, you can generate an ODT class declaration method (in the same
language as the current TestComplete project) with some level of customized commenting. Once you run the method and have
the results of the method call on the clipboard, you can paste that text back into your test project, wherever you would
wish your class declaration method to reside. With that in place, you can check the class declaration code into your source
control software along with the rest of your TestComplete projects. When you wish to modify the class, you can either alter
the generated code (for instance, to re-order the class' properties) and then run the declaration method (thereby overwriting
the ODT class in the ODT panel) or you can alter the class in the ODT panel, and then run the ODT Generator plug-in again.
To address our second scenario, that of sharing an ODT class between projects of different language types, we change our
approach, but only slightly. Rather than calling GenerateDeclaration, we instead call GenerateDeclarationToLanguage(),
passing in the name of the ODT Class to process, and also one of the TestComplete language ID constants (for instance,
ODTDeclarationGenerator.JSCRIPT_GUID). The ODT Generator plug-in will process the indicated class, and generate the output
code in the language you specify. Thus, if you have an ODT class created in a DelphiScript project and wish to have a
Jscript declaration of the class, you could call:
To install the plug-in, download the zipped archive rkl_ODTGenPlugin.zip, extract the contained PLS file into TestComplete 4BinExtensions
folder, then use TestComplete's File|Install Extensions... menu item to add the extension to TestComplete.
For the sample project suite, download and extract to your preferred location; open in TestComplete.
Caveats and errata
There are two issues to be aware of:
- At the time of the writing of this article, the TestComplete 4.25 plug-in SDK contained an issue where in the value null for Jscript, C++Script and C#Script was generated as "Null" (with an upper-case "N"). You'll find this issue rather quickly since the generated code will not run. Simply replace "Null" with "null" and the declaration method should work correctly.
- VBScript support is not in place as of the time of this writing. I decided to go ahead and release the article and plug-in in this state, planning to have VBScript support in place in time for the next article, which will discuss the source code of the plug-in.