Forum Discussion

tristaanogre's avatar
tristaanogre
Esteemed Contributor
9 years ago

Data Reflection implementation in TestComplete

This is probably a bit of an advanced question.

For some time now, I've been tweaking and working on a Table-driven Framework that I wrote with TestComplete using JScript and CSV files for the data sources. It works pretty well as it is. However, one of the challenges that this framework has is that, for each keyword in the table of test steps, I need to add another case to a switch statement in a particular code unit. For small projects with a minimal variety of test steps, this isn't a problem. But when you get to large enterprise systems with complex applications, many different forms and entry points into the test cases, etc., those switch statements are going to get MASSIVE and an absolute bear to maintain.

Something that I've been playing around with is implementing something like reflection as it is implemented in Java SE8. While the JavaScript support in TC 12 makes it such that I can create more complex classes in TC, one thing it doesn't have is the ability to have compiled "classes" that I can utilize for something like the "Class.forName()" implementation in Java. Now, again, I can probably do this by creating multiple units, each effectively creating on of the JavaScript/JScript prototype classes that I'm already using, but the maintenance of this would be that, each time I need to add a new class, I need to add another "USEUNIT" line to my main script unit. This is not undoable but I guess I'm looking for ideas of how else to implement this.

Another option I've thought of is that, each time I need to create a new class for a test type, create it as a TestComplete Script extension. I think that this will work as long as I can get the script execution methods to call properly. 

 

I guess I'm looking for help brainstorming how to simulate Data Reflection in the most efficient way possible. I'm more of a script developer and TC expert and rather junior when it comes to actual software development so I'm hoping that there are some experts out here who can give me a brain boost in my investigation.

Thanks!

  • OK... I think I have something now... I'm going the script extension route. Basically, I created an extension with a single exposed method with two parameters. The first parameter is the class name, the second parameter is a pipe delimited list of data fields to represent the properties of the object. When the method is called, using Runner.CallMethod, it returns the JScript prototype object that is declared within a unit with the same name as the class name and having the properties as defined by the pipe delimited list.


    The idea is that, if I need to add a new keyword to my data tables to execute a new test type, rather than adding a new case to my switch statement, I add a new code unit for the test type containing the necessary code to execute the test step. This eliminates the need for the different case statements and modularizes the framework even further. The script extension is generalized enough that you can do this for any class you want. I am passing in the properties list because 

     

    Here's the code if you want it and I've attached the TCX file as well.

    [ClassFromName.js]
    //This is the code unit within the extension. It's very simple in that all
    //it does is run the "returnClass" method for the appropriate class unit
    function returnClass(className, properties){
        var classMethod = className + ".returnClass";
        return localObject = Runner.CallMethod(classMethod, properties);
    }
    
    
    [TestUnit]
    //This unit declares the class and the methods on the class using JScript/JavaScript
    //prototypes
    
    function WriteLog(){
         Log.Message(this.Field1);
         Log.Message(this.Field2);
    }
    
    function TestUnitClass(properties){
        if (VarToStr(properties) != ""){    
            for(var i=0;i<aqString.GetListLength(properties);i+=2){
                eval("this." + aqString.GetListItem(properties, i) + "=" + aqString.Quote(aqString.GetListItem(properties, i+1)))    
            }
    } this.WriteLog = WriteLog; } function returnClass(properties){ var LocalObject = new TestUnitClass(properties); return LocalObject; } [RunTests] //This is just to demonstrate how it works function TestSomething(){ var NewObject = ClassFromName.returnClass("TestUnit", "Field1|data1|Field2|data2"); NewObject.WriteLog(); }

     

    This works also if you don't pass in the property list and declare the properties elsewhere. JScript/JavaScript allows you to dynamically add properties to an object after the fact.  So, the code COULD look like this.

     

    [TestUnit]
    //This unit declares the class and the methods on the class using JScript/JavaScript
    //prototypes
    
    function WriteLog(){
         Log.Message(this.Field1);
         Log.Message(this.Field2);
    }
    
    function TestUnitClass(properties){
        if (VarToStr(properties) != ""){    
            for(var i=0;i<aqString.GetListLength(properties);i+=2){
                eval("this." + aqString.GetListItem(properties, i) + "=" + aqString.Quote(aqString.GetListItem(properties, i+1)))    
            }
        }
        this.WriteLog = WriteLog;    
    }
    
    function returnClass(properties){
        var LocalObject = new TestUnitClass(properties);
        return LocalObject;
    }
    
    
    [RunTests]
    //This is just to demonstrate how it works
    function TestSomething(){
        var NewObject = ClassFromName.returnClass("TestUnit");
        NewObject.Field1 = "Test1";
        NewObject.Field2 = "Test2";
        NewObject.WriteLog();
    }

    I'm working on implementing this in my framework right now. If you're interested in the framework and the concepts behind writing this kind of structure, register for the TestComplete 301 Academy training.

     

     

  • tristaanogre's avatar
    tristaanogre
    Esteemed Contributor

    OK... I think I have something now... I'm going the script extension route. Basically, I created an extension with a single exposed method with two parameters. The first parameter is the class name, the second parameter is a pipe delimited list of data fields to represent the properties of the object. When the method is called, using Runner.CallMethod, it returns the JScript prototype object that is declared within a unit with the same name as the class name and having the properties as defined by the pipe delimited list.


    The idea is that, if I need to add a new keyword to my data tables to execute a new test type, rather than adding a new case to my switch statement, I add a new code unit for the test type containing the necessary code to execute the test step. This eliminates the need for the different case statements and modularizes the framework even further. The script extension is generalized enough that you can do this for any class you want. I am passing in the properties list because 

     

    Here's the code if you want it and I've attached the TCX file as well.

    [ClassFromName.js]
    //This is the code unit within the extension. It's very simple in that all
    //it does is run the "returnClass" method for the appropriate class unit
    function returnClass(className, properties){
        var classMethod = className + ".returnClass";
        return localObject = Runner.CallMethod(classMethod, properties);
    }
    
    
    [TestUnit]
    //This unit declares the class and the methods on the class using JScript/JavaScript
    //prototypes
    
    function WriteLog(){
         Log.Message(this.Field1);
         Log.Message(this.Field2);
    }
    
    function TestUnitClass(properties){
        if (VarToStr(properties) != ""){    
            for(var i=0;i<aqString.GetListLength(properties);i+=2){
                eval("this." + aqString.GetListItem(properties, i) + "=" + aqString.Quote(aqString.GetListItem(properties, i+1)))    
            }
    } this.WriteLog = WriteLog; } function returnClass(properties){ var LocalObject = new TestUnitClass(properties); return LocalObject; } [RunTests] //This is just to demonstrate how it works function TestSomething(){ var NewObject = ClassFromName.returnClass("TestUnit", "Field1|data1|Field2|data2"); NewObject.WriteLog(); }

     

    This works also if you don't pass in the property list and declare the properties elsewhere. JScript/JavaScript allows you to dynamically add properties to an object after the fact.  So, the code COULD look like this.

     

    [TestUnit]
    //This unit declares the class and the methods on the class using JScript/JavaScript
    //prototypes
    
    function WriteLog(){
         Log.Message(this.Field1);
         Log.Message(this.Field2);
    }
    
    function TestUnitClass(properties){
        if (VarToStr(properties) != ""){    
            for(var i=0;i<aqString.GetListLength(properties);i+=2){
                eval("this." + aqString.GetListItem(properties, i) + "=" + aqString.Quote(aqString.GetListItem(properties, i+1)))    
            }
        }
        this.WriteLog = WriteLog;    
    }
    
    function returnClass(properties){
        var LocalObject = new TestUnitClass(properties);
        return LocalObject;
    }
    
    
    [RunTests]
    //This is just to demonstrate how it works
    function TestSomething(){
        var NewObject = ClassFromName.returnClass("TestUnit");
        NewObject.Field1 = "Test1";
        NewObject.Field2 = "Test2";
        NewObject.WriteLog();
    }

    I'm working on implementing this in my framework right now. If you're interested in the framework and the concepts behind writing this kind of structure, register for the TestComplete 301 Academy training.