Forum Discussion

scottb's avatar
scottb
Contributor
12 years ago

Exception handling: Change from JScript to Delphiscript?



I have a quick question about exception handling in Delphiscript. 


 


I recently inherited about 2,000 KDT scripts with about 100 simple VBScript support functions.  I translated all of the VBScripts to JScript because I am working on a richer and more powerful set of support functions.  I am also doing a GUI map pilot.  I chose JScript because it seemed like it would do better exception handling.  


 


After spending a couple of weeks with JScript it turns out that JScript does not support excections between units.  This is something that should have been documented better in the TC docs, like maybe in the Language Reference where everyone looks for technical info about a language instead of buring that critical fact deep inside the other docs.  Ahem!


 


I spent enough time to build a proof of concept using JScript.  But to get it to handle exceptions I had to throw a bunch of kludges into my scripts.  I wrote one function to test whether a returned object looks like an exception.  It is placed into a "common" unit and included in all of my scripts.  I wrote a trap function that uses the previous one to determine if an object is an exception and if it is it throws an exception in the local unit.  I place a copy of this in the top of all of my scripts.  I place every statement that could produce an exception into the argument for the trap function.  Every place where I would normally throw an exception, I return an Error instead.  This adds to maintainance costs, and the code just looks stupid. 


 


 


----


Unit GUIMAP


 




//USEUNIT Common


//USEUNIT GUIMapCommon


... etc.


function trap(result) {


  var exptn;


  


  if (isException(result)) {


    exptn         = new Error();


    exptn.name    = Common.tErr.name;


    exptn.number  = Common.tErr.number;


    exptn.message = Common.tErr.message;


    Common.tErr   = null;


    throw exptn;


  }


  return result;


}


... etc.


function PersonForm() {


  var fnName = arguments.callee.toString().match(/^function ([^\(]+)/)[1];


  


  var win;


  var msgStr;


  


  try {


    NameMapping.Sys.MYAPPSuite.Refresh();


      


    win = NameMapping.Sys.MYAPPSuite.frmFPCARApplication.MDIClient.fBasePerson.GetUnderlyingObject();


    if (!win.Exists) throw new Error(fnName+": Unable to get object NameMapping.Sys.MYAPPSuite.frmMYApplication.MDIClient.fBasePerson");


    trap ( this.WinButton = new FormWinButtons(win) );


 


// AltTextField() is in unit GUIMapCommon.


    trap ( this.Address       = new AltTextField(win,"~a") );


    trap ( this.Email         = new AltTextField(win,"~p~e") );


    trap ( this.FirstName     = new AltTextField(win,"~p~i") );


    trap ( this.Gender        = new AltComboBox(win, "~g") );


    trap ( this.LastName      = new AltTextField(win,"~p~s") );


... etc.


  } catch (e) {


msgStr = "GUIMAP."+fnName+": "+formattedError(e);


    Log.Warning(msgStr);


    return new Error(msgStr);


  }


}




 


---


UNIT MAIN


 




//USEUNIT Common


//USEUNIT GUIMap


... etc.


function trap(result) {


  var exptn;


  


  if (isException(result)) {


    exptn         = new Error();


    exptn.name    = Common.tErr.name;


    exptn.number  = Common.tErr.number;


    exptn.message = Common.tErr.message;


    Common.tErr   = null;


    throw exptn;


  }


  return result;


}


... etc.


function CreatePersonUsingAllFields(doTranDiff, doTranDiffUpdate) {


  var fnName = arguments.callee.toString().match(/^function ([^\(]+)/)[1];


 


  var testFinished=false;


  ... etc.


  try {


... etc.


    Log.Message(fnName+": Entering data into the required fields.");


    try {


      trap ( MYAPP.PersonForm.FirstName.Write(tFirstName)  );


      trap ( MYAPP.PersonForm.LastName.Write(tLastName)   );


    } catch(e) {


      errMsg = fnName+": Error populating person form fields. "+formattedError(e);


      throw new Error(9991,errMsg);


    }


    


    Log.Message(fnName+": Entering data into the nonrequired text fields.");


    try {  


      trap ( MYAPP.PersonForm.Prefix.Write(tNamePrefix)  );


      trap ( MYAPP.PersonForm.MiddleInitial.Write(tMiddleInitial)  );


      trap ( MYAPP.PersonForm.Suffix.Write(tNameSuffix)  );


      trap ( MYAPP.PersonForm.Address.Write(tAddress)  );


      trap ( MYAPP.PersonForm.Email.Write(tEmailAddress ) );


      trap ( MYAPP.PersonForm.Gender.Write(tGender) );


      trap ( MYAPP.PersonForm.Birthdate.Write(tBirthDay) );


 ... etc.


} catch(e) {


      Log.Error(fnName+": Error populating person form fields. "+formattedError(e));


    }


    testFinished=true;


  } catch(e) {


    testFinished=false;


    Log.Error(fnName+": Fatal error. "+formattedError(e));


  } finally {


    if (!testFinished) 


      Log.Error(fnName+": Test failed.  Not all test points were executed.");


  } 




 


Now I am looking into translating all of my JScript stuff into Delphiscript.  I have three issues here.  


 


First, I already changed about a hundred mostly small function from VBScript to JScript.  I don't want to commit to Delphiscript and find out partway in that Delphiscript has a fatal issue like JScript does.  Is anyone aware of any "gotchas" with Delphiscript, especially with respect to exception handling in a complex scripts?  


 


Second, I have to explain to my management why I should spend resources moving from JScript to Delphiscript after I already spent a lot of resources for moving from VBScript to JScript.  Are there any other advantages to using Delphiscript besides just cleaning up the exception handling?  Isn't Delphiscript something Smart Bear created, and wouldn't it be "closer to the metal" than tacked on languages such as JScript and VBScript, and therefore more likely to work well with the TestComplete application?


 


Third, I'm guessing that this is maybe a typo, the TestComplete documentation discussing ODT has a section on "Creating Classes Programmatically," and "Creating Objects Programmatically," and their examples demonstrate how to use ODT methods in Visual Basic, Java, Delphi, and C# directly.  Can I really work with TestComplete methods through those languages?  If I can, is it possible to do all of my testing using Java or C# instead of one of the scripting languages?  I would prefer to work with a regular programming language, especially Java, instead of one of the scripting languages.


 


By the way, my primary test application is a Delphi app.  It is supplemented with a Flex based web app.  


 


Your feedback will be greatly appreciated.  


 




  • Philip_Baird's avatar
    Philip_Baird
    Community Expert

    Hi Scott, something else that you may or may not be aware of that is related to the Exception problem is that because JScript Script Units run in their own sandbox, augmentation of native JScript object only apply to the Script Unit they are defined in.


     


    E.g. The version of JScript in Test Complete has no Array::indexOf() function, therefore I augmented the Array prototype in a common Script Unit as such


     



    // Augment JScript Array with an indexOf function


    Array.prototype.indexOf = function( item )


    {


      for ( var j = 0; j < this.length; j++ )


      {


        if ( ( j in this ) && ( this[ j ] == item) ) return j;


      }


      return -1;


    };



     


    Unfortunately, this augmentation only applies in the defining Script Unit so Arrays created in other Script Units do not have the indexOf() function


     


    To work around this, the Script Unit needs to supply a factory function which returns an instance of the augmented Array to other Script Units, as such


     


    // Factory function to create instances of the augemented Array


    function arrayFactory( init ){


      if( init ){


        return new Array().concat( init );


      }


      return new Array()


    }


     


    Anyway, you may already be aware of this but it is gotcha in a similar vein to Exception handling.

  • Philip_Baird's avatar
    Philip_Baird
    Community Expert
    Hi Scott, before you ditch JScript, you may want to consider the following pattern I use to handle Exceptions between Script Units that allows the usage of try catch blocks without to much extra overhead.






    function Catcher() {


      var result;


      try {


        result = Thrower();


        // If result was assigned and it has an Exception property, an Exception was


        // returned so throw it to be caught in the catch block


        if( result && result.Exception ) { throw result.Exception };


        // If we get here, no Exception was thrown


        Log.Message( result );


      } catch( exObj ) {


        Log.Message( exObj.message );


      }


    }



     


    function Thrower() {


      try {


        // Will throw a Object doesn't support this property or method Exception


        this.notLikelyToExist();


        // Because of JScripts loose typing, functions can return different types which


        // allows this pattern to return success values as well as Exceptions


        return 123;


      } catch( exObj ) {


        // Wrap the Exception in an Object Literal with an "Exception" (or similar)


        // property so it can be checked for in calling code


        return { "Exception": exObj };


      }


    }




    This can be expanded upon (as I have done) to utilise proper Exception classes instead of Object Literals for full "instanceof" Type checking.



    Anyway, just a thought, it may be of some use.
  • Thank you Phil.  That is basically what I am doing.  My test for exception is in a Common unit that gets included in all of my other units.  It looks like this: 




    // Tests whether an exception has occurred since Testcomplete does not handle this correctly. 

    // Returns true if res in a real or fakie exception.  Sets Common.tErr to the error.


    function isException(res) {


      var objType = Object.prototype.toString.call(res);


      


      // Workaround to handle errors from EventHandlers. EventHandlers do not return values or exceptions.

    // Eventhandlers set the values in Common.err when they have an exception.


      if (Common.err!=null) {  // Common.err is set in EventHandlers.


        Common.tErr = new Error(Common.err.number,Common.err.message);


        Common.tErr.name = Common.err.name;


        Common.err = null;  // Set it to null for the next trap.


        return true;


      }


      


      if (res==null) 


        return false;


     


      if (objType!="[object Object]" && objType!="[object Error]") 


        return false;


          


      // Workaround to handle JScript errors from external units.  


      // Do not override the name property if you create a new type of error.


      if (res) { 


        if (res.hasOwnProperty("name") || aqObject.IsSupported(res, "name")) { 


          if (     res.name=="Error"


                || res.name=="EvalError"


                || res.name=="RangeError"


                || res.name=="ReferenceError"


                || res.name=="SyntaxError"


                || res.name=="TypeError"


                || res.name=="URIError"


              ) {


            Common.tErr = new Error(res.number,res.message);


            Common.tErr.name = res.name;


            return true;


          }


        } 


        if (res.hasOwnProperty("isError")) { // Add isException to all custom Errors so they get trapped.


          Common.tErr = new Error(res.number,res.message);


          Common.tErr.name = res.name;


          return true;


        } 


      }  


       


      // Handle errors local to Common.    


      if ( res && res instanceof Error) { 


        Common.tErr = res;


        return true;


      }


      return false;


    }


    function testIsException() {


    var res;


       Log.Message(isException(res));


       Log.Message(isException(null));


       Log.Message(isException("string"));


       Log.Message(isException(1));


       Log.Message(isException({}));


       Log.Message(isException(new Error()));


       Log.Message(isException(new SyntaxError()));


    }


     



    I looked into Delphiscript and decided not to use it.  It isn't object oriented, and I wanted something OO to make it easy to do GUI mapping.  To do OO I would probably need to misuse ODT, which would have introduced its own clunkiness.



    I believe VBScript is more cumbersome that JScript for exception handling, even with the clunky JScript trap() function wrapped around everything that could throw an exception.  I do not want to use VBScript. 



    I looked at ways to use high level languages like Java.  TestComplete does allow some access as Connected Applications.  If Java worked well with COM I might have used it.  I don't think my QA department can hire good experienced high level language staff right now.  So I ruled out that approach.



    I'm going to stay with JScript even though it breaks the gracefullness of my design by making me put the silly looking trap() function around most of my test statements.  It seems to be the least worst approach.   

  • simon_glet's avatar
    simon_glet
    Regular Contributor
    Hi Scott,



    We use JScript and instead of implementing a workaround to the exception handling limitation we went for defensive programming, C style with no try-catch exception handling. When an unforseen issue occurs the script crashes, the application is reinitialized and the next test is executed.

    After some time we noticed that the code was cleaner and easier to understand that way.



    It is a matter of taste so this was about giving another perspective ;-)



    Sincerely
  • Thanks Phil.  I read this in the docs somewhere and I didn't understand it.  Now it makes sense.