Showing posts with label Featured. Show all posts
Showing posts with label Featured. Show all posts

Open UI: Testing with Selenium

Selenium is a browser automation tool, that allows web projects to automate repetitive tasks. As an early pioneer of Open UI, I found this new capability to be incredibly useful for achieving continuous integration. I gave readers a hint of this last year, in the article "Open UI - Build Process", which left readers with a list of ingredients, and a plan to manage their build automation.

Now that more Siebel projects around the world are on embarking on upgrading their Siebel Applications to Siebel Open UI. Its about time we advance this concept into something more tangible that Clients can benefit from.

Picture this... as part of the Open UI upgrade, you are required to navigate to 1000+ views in Siebel and test for WCAG defects in all 3 major browsers. That isn't so exciting.

Testing for Open UI defects requires a high level of thoroughness to ensure that entire your Application is compliant, and remains compliant in the future. The move to Open UI will expose poor configuration practices that might have been passable in HI, but will break in Open UI. Open UI will also introduce defects as a side effect of the upgrade.

The simple approach to this problem is to brute force it, and assign a team of developers to navigate to each view to analyse for technical defects. But this isn't really viable, as a long term strategy, a smarter approach is to use web automation.

Web automation brings to mind images of robots that are used to scrape web sites, harvest email addresses and index content, but Siebel developers have a more important itch to scratch: Testing the Siebel UI.

The obvious use case is to run continuous integration testing, to ensure that new builds are thoroughly tested over night. A more advanced crawler can be built to perform functional testing of the applications main areas, but the scope of this article is to show how you can build your own Open UI crawler that can be used to programatically navigate to each view, and optionally validate your application.

Theres no shortage of tools in the market that is available to perform this sort of work, but if we narrow our criteria to open source web automation solutions, the Selenium web driver makes a pretty good choice, as it's also set to become a W3C recommendation. Selenium works with all the major browsers, is compatible with your favorite programming language, and it is also free.

This article will provide you with a sample application that implements Open UI automation with Ruby, but you can extract the lessons learnt from this article, and implement them in your language of choice.

I've chosen to use Ruby as my language, because there is solid support for web automation. Ruby has gems that allow the developer to easily parse the DOM, make selections using CSS or XPath syntax, deal with dialog boxes, and take screen shots of problem views. Combining with this Watir, which is a selenium wrapper in Ruby, allows the developer to build the automation quickly in a light weight language.

The solution for your project may be different, if you require enterprise support, headless servers, or if you plainly prefer to stick to your language of choice, because of your available skill set, then the right tool for your circumstance will be different. The most important ingredient here is Selenium, or in this case Watir, which is a Ruby flavor of Selenium.

Selenium

This is the Browser automation API that allows you to control your browser programatically

http://www.seleniumhq.org/

" Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) also be automated as well."

Browser Support

Chrome, Firefox, IE...

http://www.seleniumhq.org/about/platforms.jsp#browsers

Remote control drivers

Javascript, Java, C, Ruby, Perl, PHP...

http://www.seleniumhq.org/about/platforms.jsp#programming-languages

Open UI

The crawler can be run against a thin client, or against a local thick Client running Open UI.

Ruby Implementation

Knowing Ruby isn't a prerequisite to understand this concept. Ruby is a pretty readable language, and I've added a good dosage of comments to explain what the code is doing so those from non Ruby backgrounds can follow.

#import required libraries
require 'rubygems'
require 'watir-webdriver'
require 'cgi'

#Parametise the URL that will be used to navigate to the Open UI application
#This should really be configured for a server URL for real testing
#but im using a local URL for this example
sURL = "http://localhost/start.swe?"
sPass = nil
sUser = nil

#Open a new chrome browser session
browser = Watir::Browser.new :chrome

#navigates to the URL defined above
browser.goto sURL

#maximise the window so we can see all entire view
browser.window.maximize

#This block simulates the user login process for thin client connection
#else it is bypassed for the thick client
if sUser!=nil && sPass!=nil
     #fill in username in the user name field
     username = browser.text_field(:name, "SWEUserName")
     username.set sUser if username!=nil
     #fill in password in the password field
     password = browser.text_field(:name, "SWEPassword")
     password.set sPass if password!=nil
   
     sleep 1
     #click the login button
     browser.link(:text =>"Login").when_present.click
end
sleep 5


Congratulations!. At this point we have just created a simple crawler that has logged into an Open UI Application.

Next we want to tell it how to navigate the application, by going to the sitemap, read all the views, and parse all the JS links embedded in the view elements. This is done pretty easily in Ruby.

     #use CSS to locate sitemap icon, and click it 
     browser.element(:css => "li[name=SiteMap] > img").when_present.click

     #use CSS selector to read all the views in sitemap into an array
     #these links also contain an onclick attr that allows to emulate a view navigation
     links =browser.css("span[class='viewName'] > a")

Finally, we loop through every link, execute the JS code to perform the navigation, and optionally perform any automation.

     links.each do |link|
     params = CGI.parse(link.attr("onclick"))
     sleep 5
     #Goto view
     browser.execute_script( link.attr("onclick") )
     end

Thats as simple as it needs to be.

To fire it up with a dedicated client, you'll first need to open up an existing session to start the local web server. When the above code is run, it will launch a new browser connecting to the existing session.

For thin client connections, it is not necessary to pre-launch the session, the above code will instantiate a new browser session, and will login in normally.

A crawler like the program above, can navigate the application, verify each view and flag views that have issues.

Building a simple crawler is a easy, and is something that you can give to an energetic graduate to perform in a day, however if you require a crawler that can be a workhorse for your Continuous Integration strategy, then it needs to be a little more robust, and more scalable than the simple example above, or if your needs warrant a more specialized crawler, that can check for and enforce WCAG compliance in your application, then you'll ideally need a plugin system that can allow you to easily add new validators. If want to go a little further, and build in functional testing capabilities, then could build an API bridge to facilitate a DSL to build more readable test cases.

Open UI + selenium opens up these exciting opportunities. For my client, having a nightly build process + continuous integration, and WCAG reporting, ensures they have a strict standards compliant UI and a more stable application for every deploy.

This article provides the necessary ingredients, and a simple recipe for other Siebel customers to follow the same path.

Further Reading

Selenium Web driver
Continuous Integration
WCAG

More on Open UI

Serious Open UI developers should be on the watch, for the up comming Open UI book from some very distinguished authors

Siebel Open UI Developer's Handbook

A Better XML Logger

This article is Part 3 of Siebel Error Handling Approach

Introduction

Last time we looked at an XML logger implementation using the vanilla "EAI XML Queuing Service", and ran through its shortfalls. This time we go through the design of a better XML logger, and introduce a new XML tagging feature.

The keys to building a better XML Logger comes back to understanding how the EAI XML Queueing Service works. The Queueing service takes a SiebelMessage, converts it from a PropertySet to an XML document, and saves it to a file attachment BC, all of which are standard Siebel operations. So we can use this design as the basis of our own EAI XML Queueing service implementation.

High Level Design

1. Create an IO around the EAI Queue/Item BC
2. EAI Queue Item will store References to our Error table, and XML Tagging information
3. Implement an XML query capability to extract information from the message
4. Dynamically Generate a Siebel Message around the EAI Queue structure, with our XML message as an attachment
5. Use EAI Siebel Adaptor to insert the XML message
6. Put a view on top of the above components to allow association to the error table, and to support querying through the UI

Build Steps

1. Create a new WF: "A Better XML Queuing Process"

This workflow has to support logging of either a SiebelMessage, XML document, or any other type of interface message Eg. If a SiebelMessage is provided, this has to be converted to XML Doc.

2. Generating the EAI Queue SiebelMessage

This WF step generates a SiebelMessage that contains the above interface message as an attachment.

There are 3 mains method for creating an instance of this IO

1. XSLT
2. PRM ANI Utility Service
3. Scripting

PRM ANI Utility will easily generate a SiebelMessage structure which is 90% of your work, but it will not generate the File Attachment IC, so it has to be complemented with some scripting, or go for the declarative option using XSLT, which will allow us to generate a message complete with the Attachment IC without resorting to scripting.

3. Populating the SiebelMessage

This WF step populates the SiebelMEssage with actual data and will require some sort of transformation, so you have the following options

1. Data Mapper
2. XSLT
3. Scripting

Once you have your SiebelMessage, using Data Mapper with inputs should be straight forward, however, using the XSLT method requires a little more work, because you will need a source document that contains all your source values, plus the XML payload. This can be achieved by using an empty SiebelMessage, and using dot notation to inject all your variables, including the XML payload, into the SiebelMessage header as attributes. These attributes would then be available in your source document to complete the transformation.

The following, is a list of the attributes required to produce an IO with attachment

XML Payload
Comments
Queued Timestamp
Reference Id
Reference Val2
Reference Val3
Sequence Number
Status
Queue Name
AttachmentIsTextData
Extension
MsgFileAutoUpdFlg
MsgFileDate
MsgFileDeferFlg
MsgFileDockReqFlg
MsgFileDockStatFlg
MsgFileExt
MsgFileSrcType
MsgFileName

The vanilla EAI XML Queueing Service produces attachments with a rigid format similar to this

s_eai_queue_itm_1-62O6DB.txt

The file type is hard coded to txt, and the filename cannot be controlled

The "MsgFileName" is of particular interest to us, as it allows us to produce a more meaningful filename.

4. Insert SiebelMessage

At this point, the populated SiebelMessage is ready to be Inserted via EAI Siebel Adaptor.

5. Build a nice UI to retrieve these XML logs, in association to its Error log.

Our XML files are saved to the Siebel file system, allowing users to retrieve the XMLs without going through file system permissions.

Message Tagging

Message Tagging provides the ability for a project to peek inside the XML message while in transport, and log certain elements/attributes for searching. This was one of the downfalls of EAI Queueing Service, as key details of the message were hidden, until you opened each message individually. This capability is important because Siebel zips the files stored in its attachment folder, and the contents become unsearchable, unless we tag it.

The following XML is a sample of an image notification message. The key pieces of information to be tagged from this message are the Id, PartyType, SourceSystem, and NewImageFlag.
<?xml version="1.0" encoding="UTF-8"?>
<PartyUpdate>
   <PartyIdList>
       <PartyIdDetails>
           <Id>1-XXXX</Id>
           <Type>Siebel</Type>
       </PartyIdDetails>
   </PartyIdList>
   <PartyType>PERSON</PartyType>
   <EventSource>ESB</EventSource>
   <SourceSystem>Imagr</SourceSystem>
   <NewImageFlag>true</NewImageFlag>
</PartyUpdate>
The schema would obviously allow for other types of notifications, and the Message Tagging capability would need to be flexible enough to cater for these other schema options. Message Tagging can be implemented with different kinds of tools, but at the heart of it, we need to query data from the XML.

A few Siebel favourites which can be used for deep hierarchy extraction are

1. FINS Industry XML Query Service
- Allows XPathish notation to be used

2. PRM ANI Utility Service.GetProperty
- Supply the IC name, and property to be extracted

3. WF Alias
- Similiar to Dot notation, but without the 75 character limitation

To be honest, none of the above options are particularly flexible, especially for dealing with namespaces, repeating groups, filtering, getting the nth child, dynamic querying etc. There isn't any vanilla querying capability, that meets this particular requirement.

So for your more ambitious Error handling needs, I would go for a Java based solution, that implements the full XPath standard, allowing all of the previous problems to be addressed. This would be implemented in a JBS, which accepts two arguments the XML, the XPath expression, and returns the XPath result.

To follow our theme of flexibility, the system shouldn't be hard coded to look for certain elements, the design should allow the customer to configure XPath statements, and associate them with an Interface, and maybe more specifically an Interface direction. Going down this path, takes us down a level of abstraction that is not available out of the box, which is a good thing, except for the poor designer who has to materialise this concept!

Conclusion

In this series on Error Handling in Siebel, we've taken a look into the key features of an Error Handling framework. We also delved into the complex error handling requirements of an EAI professional, dissected EAI Queueing Service, and provided the map for framework designers to build a better XML Logger. For an alternative approach, fellow Siebel expert Mik, also provides a different design that readers should also consider.

The capability to capture an XML message, along with its associated error detail, should be a core part of every projects toolset and there should be no doubt, on the importance of this feature. Error Handling Frameworks are not new to Siebel, but they are only available behind closed doors, so the comments are open to readers.

Do you have classy Error Handling ideas that you would like to share with the rest of the world?

Custom eScript Engine

This long overdue article, is a continuation of one of my earliest articles at Impossible Siebel, how to run eScript on a Siebel server without compiling.

The ability to write eScript, and test it without compiling, was introduced in Siebel 8 with "Fix and Go". Convenient as it sounds, Fix and Go, isn't the ultimate solution.

Even though you don't have to compile, it is still necessary to launch a debugging session from a thick client to test the code. To unit test a stand alone business service, it is more appropriate to use Business Service simulator, which allows the developer to create client side code, and test it it on the fly. Business Service Simulator is useful, but using it, is like using Notepad to write code, it suffices but isn't user friendly. Business Service Simulator by its own nature cannot test UI context, It is also quite clunky, for testing small snippets of code, which is where the original article took a point, providing the developer with an alternative to write eScript on the fly in their favourite text editor, and test it without compiling.

This article is a walk through for the advanced reader, who wants run through the implementation process to build such a tool. I'll also provide the full source code to the translation class, that makes it possible for us to emulate the Siebel eScript API.

Pre-requisites

The following will seem familiar if you've gone through my article on a creating a Java Business Service.

1. Java for Dummies

Learn some Java

2. Sun Java Software Development Kit

Goto the sun website, download and install the SDK

http://java.sun.com/javase/downloads/index.jsp

3. Java IDE

Download and install your favourite Java IDE.

4. Siebel Enterprise

This method will only allow you to run eScript on the server, and not on a local machine, so we need a Siebel Server installation to connect to.

Implementation

1. Connect to Siebel using the JDB Interface

Ensure you have read the following article on Support Web, this will provide the foundations for our eScript program.

How To Use Siebel Java Data Bean [ID 476902.1]

I've run through the instructions again and started from scratch, to re-build this program for this article, and found the Siebel documentation to be very good.

Once you have followed the instructions on this document, you should have a working Java Data Bean (JDB) connection to your Siebel server.

2. Import ECMA scripting engine

private ScriptEngineManager oScptMgr    = new ScriptEngineManager();
private ScriptEngine        jsEngine    = oScptMgr.getEngineByName("ECMAScript");//Instantiate base engine


Siebel is based on the ECMA standard, so we are importing the ECMAScript engine to provide us with the base language, to which we will add the eScript translation layer.

3. Pass Siebel Java application object into ECMA engine

jsEngine.put("oApp",m_dataBean);
//oApp is the Siebel Java Application and we want to load this into the ECMA engine


We need to pass the Siebel application reference, that we have instantiated earlier following the Siebel tutorial, into the ECMA engine instance that we created above.

This statement creates a global object inside our ECMAEngine called oApp that references the m_dataBean handle in our Java program. This allows our ECMA engine to access all the methods available in the JDB Interface, using a scripting language.

Nice! now we have an ECMA language with the Siebel JDB API as a global.

4. Create a class abstraction layer to translate the eScript language into JDB methods.

This is the custom eScript API, that I built several years ago, but never got around to publishing. It is provided as is, with no warranties, or support, and use of it, is at your own risk.
                       
/**************************************************************************************************************
* Component Name        :  Impossible Siebel eScript Translation Class
* Component Information :  Provides a wrapper for the Siebel JDB methods
* Date  Author
* 2009  Jason Le 
* ============================================================================================================
* 01MAR09 JLE  Initial version
**************************************************************************************************************/
//Define Escript Constants
var ContinueOperation = true;       var CancelOperation       = false;

//ExecuteQuery constants
var ForwardOnly       = true;       var ForwardBackward       = false;
var NewAfter          = true;       var NewBefore             = false;

//SetViewMode
var SalesRepView      = 0;          var OrganizationView      = 5;
var ManagerView       = 1;          var GroupView             = 7;
var PersonalView      = 2;          var CatalogView           = 8;
var AllView           = 3;          var SubOrganizationView   = 9;

var Inputs            = oApp.newPropertySet();
var Outputs           = oApp.newPropertySet();
var arrDefaultInput   = [];
var Appinstance       = 0;
var gApplication;

//TheApplication() Class
function TheApplication() {
  if (gApplication == null) {
    Appinstance = Appinstance+1;
    gApplication = new class_app(oApp);
  }
  return gApplication;
}

//class_app.prototype = oApp;
class_app.prototype.GetProfileAttr          = class_app_GetProfileAttr;
class_app.prototype.SetProfileAttr          = class_app_SetProfileAttr;
class_app.prototype.Trace                   = class_app_Trace;
class_app.prototype.TraceOff                = class_app_TraceOff;
class_app.prototype.TraceOn                 = class_app_TraceOn;
class_app.prototype.PositionId              = class_app_PositionId;
class_app.prototype.PositionName            = class_app_PositionName;
class_app.prototype.LoginId                 = class_app_LoginId;
class_app.prototype.LoginName               = class_app_LoginName;
class_app.prototype.InvokeMethod            = class_app_InvokeMethod;
class_app.prototype.GetBusObject            = GetBusObject;
class_app.prototype.GetService              = GetService;
class_app.prototype.NewPropertySet          = NewPropertySet;

function class_app(application)             {if(application != null){this._application = application; }}
function class_app_GetProfileAttr(v)        {return this._application.getProfileAttr(v) ;} //+ '';}
function class_app_SetProfileAttr(n,v)      {return this._application.setProfileAttr(n,v);}
function class_app_Trace(v)                 {trace(v);}
function class_app_TraceOff()               {return this._application.traceOff();}
function class_app_TraceOn(f,t,s)           {return this._application.traceOn(f,t,s);}
function class_app_PositionId()             {return this._application.positionId() ;} //+ '';}
function class_app_PositionName()           {return this._application.positionName() ;} //+ '';}
function class_app_LoginId()                {return this._application.loginId() ;} //+ '';}
function class_app_LoginName()              {return this._application.loginName() ;} //+ '';}
function class_app_NewPropertySet()         {return this._application.newPropertySet();}
function class_app_InvokeMethod(){
var sMethod = arguments[0];
var aInput  = [];
  for(i=1; i<arguments.length; i++){
    aInput.push(arguments[i])
  }
  return this._application.invokeMethod(sMethod, aInput);
}



//NewPropertySet() Class
//class_PS.prototype = new class_PS(oPS);
class_PS.prototype.PropertyExists           = class_PS_PropertyExists;
class_PS.prototype.GetProperty              = class_PS_GetProperty;
class_PS.prototype.getProperty              = class_PS_GetProperty;
class_PS.prototype.SetProperty              = class_PS_SetProperty;
class_PS.prototype.setProperty              = class_PS_SetProperty;
class_PS.prototype.GetValue                 = class_PS_GetValue;
class_PS.prototype.SetValue                 = class_PS_SetValue;
class_PS.prototype.GetType                  = class_PS_GetType;
class_PS.prototype.SetType                  = class_PS_SetType;
class_PS.prototype.Copy                     = class_PS_Copy;
class_PS.prototype.Reset                    = class_PS_Reset;
class_PS.prototype.GetChild                 = class_PS_GetChild;
class_PS.prototype.AddChild                 = class_PS_AddChild;
class_PS.prototype.RemoveChild              = class_PS_RemoveChild;
class_PS.prototype.RemoveProperty           = class_PS_RemoveProperty;
class_PS.prototype.InsertChildAt            = class_PS_InsertChildAt;
class_PS.prototype.GetFirstProperty         = class_PS_GetFirstProperty;
class_PS.prototype.GetNextProperty          = class_PS_GetNextProperty;
class_PS.prototype.GetPropertyCount         = class_PS_GetPropertyCount;

function NewPropertySet()                  {return new class_PS(this._application.newPropertySet() );}
//function NewPropertySet()                   {return new class_PS( oPS );}
function class_PS(PS)                       {if(PS != null){this._PS = PS;} }
function class_PS_PropertyExists(v)         {return this._PS.propertyExists(v);}
function class_PS_GetProperty(v)            {return this._PS.getProperty(v) ;} //+ '';}
function class_PS_SetProperty(n,v)          {return this._PS.setProperty(n,v);}
function class_PS_GetValue(v)               {return this._PS.getValue(v) ;} //+ '';}
function class_PS_SetValue(v)               {return this._PS.setValue(v);}
function class_PS_GetType(v)                {return this._PS.getType(v) ;} //+ '';}
function class_PS_SetType(v)                {return this._PS.setType(v);}
function class_PS_Copy()                    {return this._PS.copy();}
function class_PS_Reset()                   {return this._PS.reset();}
function class_PS_GetChild(i)               {return this._PS.getChild(i);}
function class_PS_AddChild(Ps)              {return this._PS.addChild(Ps);}
function class_PS_RemoveChild(i)            {return this._PS.removeChild(i);}
function class_PS_RemoveProperty(v)         {return this._PS.removeProperty(v);}
function class_PS_InsertChildAt(obj,i)      {return this._PS.insertChildAt(obj,i);}
function class_PS_GetFirstProperty()        {return this._PS.getFirstProperty() ;} //+ '';}
function class_PS_GetNextProperty()         {return this._PS.getNextProperty() ;} //+ '';}
function class_PS_GetPropertyCount()        {return this._PS.getPropertyCount();}



//GetBusObject() Class
class_BO.prototype.Name                     = class_BO_Name;
class_BO.prototype.GetBusComp               = GetBusComp;

function GetBusObject(BOName)               {return new class_BO(this._application.getBusObject(BOName));}
function class_BO(oBO)                      {if(oBO != null){this._BO = oBO; } }
function class_BO_Name()                    {return this._BO.name();}

//GetService() Class
class_Svc.prototype.Name                    = class_Svc_Name;
class_Svc.prototype.InvokeMethod            = class_Svc_InvokeMethod;
class_Svc.prototype.PropertyExists          = class_Svc_PropertyExists;
class_Svc.prototype.GetProperty             = class_Svc_GetProperty;
class_Svc.prototype.SetProperty             = class_Svc_SetProperty;

function GetService(SvcName)                {return new class_Svc(this._application.getService(SvcName));}
function class_Svc(oSvc)                    {if(oSvc != null){this._Svc = oSvc; } }
function class_Svc_Name()                   {return this._Svc.getName() ;} //+ '';}
function class_Svc_InvokeMethod(Meth,Ps1,Ps2){
  return this._Svc.invokeMethod(Meth,Ps1,Ps2);
}
function class_Svc_PropertyExists(v)        {return this._Svc.propertyExists(v);}
function class_Svc_GetProperty(v)           {return this._Svc.getProperty(v) ;} //+ '';}
function class_Svc_SetProperty(n,v)         {return this._Svc.setProperty(n,v);}


//GetBusComp() Class
class_BC.prototype.Name                     = class_BC_Name;
class_BC.prototype.ActivateField            = class_BC_ActivateField;
class_BC.prototype.ActivateMultipleFields   = class_BC_ActivateMultipleFields ;
class_BC.prototype.Associate                = class_BC_Associate;
class_BC.prototype.ClearToQuery             = class_BC_ClearToQuery;
class_BC.prototype.CountRecords             = class_BC_CountRecords;
class_BC.prototype.DeactivateFields         = class_BC_DeactivateFields;
class_BC.prototype.DeleteRecord             = class_BC_DeleteRecord;
class_BC.prototype.ExecuteQuery             = class_BC_ExecuteQuery;
class_BC.prototype.FirstRecord              = class_BC_FirstRecord;
//class_BC.prototype.GetAssocBusComp          = class_BC_GetAssocBusComp;
class_BC.prototype.GetFieldValue            = class_BC_GetFieldValue;
class_BC.prototype.GetFormattedFieldValue   = class_BC_GetFormattedFieldValue;
class_BC.prototype.GetMVGBusComp            = GetMVGBusComp;//class_BC_GetMVGBusComp;
class_BC.prototype.GetNamedSearch           = class_BC_GetNamedSearch;
class_BC.prototype.GetPicklistBusComp       = class_BC_GetPicklistBusComp;
class_BC.prototype.GetSearchExpr            = class_BC_GetSearchExpr;
class_BC.prototype.GetSearchSpec            = class_BC_GetSearchSpec;
class_BC.prototype.GetViewMode              = class_BC_GetViewMode;
class_BC.prototype.InvokeMethod             = class_BC_InvokeMethod;
class_BC.prototype.LastRecord               = class_BC_LastRecord;
class_BC.prototype.NewRecord                = class_BC_NewRecord;
class_BC.prototype.NextRecord               = class_BC_NextRecord;
class_BC.prototype.ParentBusComp            = class_BC_ParentBusComp;
class_BC.prototype.Pick                     = class_BC_Pick;
class_BC.prototype.PreviousRecord           = class_BC_PreviousRecord;
class_BC.prototype.RefineQuery              = class_BC_RefineQuery;
class_BC.prototype.SearchExpr               = class_BC_SearchExpr;
class_BC.prototype.SetFieldValue            = class_BC_SetFieldValue;
class_BC.prototype.SetFormattedFieldValue   = class_BC_SetFormattedFieldValue;
class_BC.prototype.SetSearchSpec            = class_BC_SetSearchSpec;
class_BC.prototype.SetSearchExpr            = class_BC_SetSearchExpr;
class_BC.prototype.SetSortSpec              = class_BC_SetSortSpec;
class_BC.prototype.SetViewMode              = class_BC_SetViewMode;
class_BC.prototype.UndoRecord               = class_BC_UndoRecord;
class_BC.prototype.ViewMode                 = class_BC_ViewMode;
class_BC.prototype.WriteRecord              = class_BC_WriteRecord;

function GetBusComp(BCName)                 {return new class_BC(this._BO.getBusComp(BCName));}
function class_BC(oBC)                      {if(oBC != null){this._BC = oBC; } }
function class_BC_Name()                    {return this._BC.name();}
function class_BC_ActivateField(v)          {return this._BC.activateField(v);}
function class_BC_ActivateMultipleFields(Ps){return this._BC.activateMultipleFields(Ps);}
function class_BC_Associate(i)              {return this._BC.associate(i);}
function class_BC_ClearToQuery()            {return this._BC.clearToQuery();}
function class_BC_CountRecords(){
  var oMVG = this._BC;
  var icount = 0;
  with (oMVG){
    executeQuery(ForwardBackward);

    var isRecord = firstRecord();
    if (isRecord)
    {
      while (isRecord)
      {
      icount++;
      isRecord = nextRecord();
      }
    }
    firstRecord();
  }
  
  return icount;
}
function class_BC_DeactivateFields()        {return this._BC.deactivateFields();}
function class_BC_DeleteRecord()            {return this._BC.deleteRecord();}
function class_BC_ExecuteQuery(v)           {return this._BC.executeQuery(v);}
function class_BC_FirstRecord()             {return this._BC.firstRecord();}
function class_BC_NextRecord()              {return this._BC.nextRecord();}
//function class_BC_GetAssocBusComp()         {return this._BC.getAssocBusComp();}
function class_BC_GetFieldValue(v)          {return this._BC.getFieldValue(v) + '';} //+ '';}
function class_BC_GetFormattedFieldValue(v) {return this._BC.getFormattedFieldValue(v) ;} //+ '';}
function class_BC_GetMVGBusComp(v)          {return this._BC.getMVGBusComp(v);}
function class_BC_GetNamedSearch(v)         {return this._BC.getNamedSearch(v);}
function class_BC_GetPicklistBusComp(v)     {return this._BC.getPicklistBusComp(v);}
function class_BC_GetSearchExpr()           {return this._BC.getSearchExpr() ;} //+ '';}
function class_BC_GetSearchSpec()           {return this._BC.getSearchSpec() ;} //+ '';}
function class_BC_GetViewMode()             {return this._BC.getViewMode() ;} //+ '';}
function class_BC_InvokeMethod(){
var sMethod = arguments[0];
var aInput  = [];
  for(i=1; i<arguments.length; i++){
    aInput.push(arguments[i])
  }
  return this._BC.invokeMethod(sMethod, aInput);
}
function class_BC_LastRecord()              {return this._BC.lastRecord();}
function class_BC_NewRecord(v)              {return this._BC.newRecord(v);}
//function class_BC_NextRecord()              {return this._BC.nextRecord();}
function class_BC_ParentBusComp()           {return this._BC.parentBusComp();}
function class_BC_Pick()                    {return this._BC.pick();}
function class_BC_PreviousRecord()          {return this._BC.previousRecord();}
function class_BC_RefineQuery()             {return this._BC.refineQuery();}
function class_BC_SearchExpr(v)             {return this._BC.searchExpr(v);}
function class_BC_SetFieldValue(n,v)        {return this._BC.setFieldValue(n,v);}
function class_BC_SetFormattedFieldValue(n,v)        {return this._BC.setFormattedFieldValue(n,v);}
function class_BC_SetSearchExpr(v)          {return this._BC.setSearchExpr(v);}
function class_BC_SetSearchSpec(n,v)          {return this._BC.setSearchSpec(n,v);}
function class_BC_SetSortSpec(v)          {return this._BC.setSortSpec(v);}
function class_BC_SetViewMode(v)            {return this._BC.setViewMode(v);}
function class_BC_UndoRecord()              {return this._BC.undoRecord();}
function class_BC_ViewMode(v)               {return this._BC.viewMode(v);}
function class_BC_WriteRecord()             {return this._BC.writeRecord();}

//GetMVGBusComp() Class
class_MvgBC.prototype.Name                     = class_MvgBC_Name;
class_MvgBC.prototype.ActivateField            = class_MvgBC_ActivateField;
class_MvgBC.prototype.ActivateMultipleFields   = class_MvgBC_ActivateMultipleFields ;
class_MvgBC.prototype.Associate                = class_MvgBC_Associate;
class_MvgBC.prototype.ClearToQuery             = class_MvgBC_ClearToQuery;
class_MvgBC.prototype.CountRecords             = class_MvgBC_CountRecords;
class_MvgBC.prototype.DeactivateFields         = class_MvgBC_DeactivateFields;
class_MvgBC.prototype.DeleteRecord             = class_MvgBC_DeleteRecord;
class_MvgBC.prototype.ExecuteQuery             = class_MvgBC_ExecuteQuery;
class_MvgBC.prototype.FirstRecord              = class_MvgBC_FirstRecord;
//class_MvgBC.prototype.GetAssocBusComp          = class_MvgBC_GetAssocBusComp;
class_MvgBC.prototype.GetFieldValue            = class_MvgBC_GetFieldValue;
class_MvgBC.prototype.GetFormattedFieldValue   = class_MvgBC_GetFormattedFieldValue;
class_MvgBC.prototype.GetMVGBusComp            = class_MvgBC_GetMVGBusComp;
class_MvgBC.prototype.GetAssocBusComp          = GetAssocBusComp;
class_MvgBC.prototype.GetNamedSearch           = class_MvgBC_GetNamedSearch;
class_MvgBC.prototype.GetPicklistBusComp       = class_MvgBC_GetPicklistBusComp;
class_MvgBC.prototype.GetSearchExpr            = class_MvgBC_GetSearchExpr;
class_MvgBC.prototype.GetSearchSpec            = class_MvgBC_GetSearchSpec;
class_MvgBC.prototype.GetViewMode              = class_MvgBC_GetViewMode;
class_MvgBC.prototype.InvokeMethod             = class_MvgBC_InvokeMethod;
class_MvgBC.prototype.LastRecord               = class_MvgBC_LastRecord;
class_MvgBC.prototype.NewRecord                = class_MvgBC_NewRecord;
class_MvgBC.prototype.NextRecord               = class_MvgBC_NextRecord;
class_MvgBC.prototype.ParentBusComp            = class_MvgBC_ParentBusComp;
class_MvgBC.prototype.Pick                     = class_MvgBC_Pick;
class_MvgBC.prototype.PreviousRecord           = class_MvgBC_PreviousRecord;
class_MvgBC.prototype.RefineQuery              = class_MvgBC_RefineQuery;
class_MvgBC.prototype.SearchExpr               = class_MvgBC_SearchExpr;
class_MvgBC.prototype.SetFieldValue            = class_MvgBC_SetFieldValue;
class_MvgBC.prototype.SetFormattedFieldValue   = class_MvgBC_SetFormattedFieldValue;
class_MvgBC.prototype.SetSearchSpec            = class_MvgBC_SetSearchSpec;
class_MvgBC.prototype.SetSearchExpr            = class_MvgBC_SetSearchExpr;
class_MvgBC.prototype.SetSortSpec              = class_MvgBC_SetSortSpec;
class_MvgBC.prototype.SetViewMode              = class_MvgBC_SetViewMode;
class_MvgBC.prototype.UndoRecord               = class_MvgBC_UndoRecord;
class_MvgBC.prototype.ViewMode                 = class_MvgBC_ViewMode;
class_MvgBC.prototype.WriteRecord              = class_MvgBC_WriteRecord;

function GetMVGBusComp(FieldName)              {return new class_MvgBC(this._BC.getMVGBusComp(FieldName));}
function class_MvgBC(oMvgBC)                   {if(oMvgBC != null){this._MvgBC = oMvgBC; } }
function class_MvgBC_Name()                    {return this._MvgBC.name();}
function class_MvgBC_ActivateField(v)          {return this._MvgBC.activateField(v);}
function class_MvgBC_ActivateMultipleFields(Ps){return this._MvgBC.activateMultipleFields(Ps);}
function class_MvgBC_Associate(i)              {return this._MvgBC.associate(i);}
function class_MvgBC_ClearToQuery()            {return this._MvgBC.clearToQuery();}
//function class_MvgBC_CountRecords()            {return this._MvgBC.countRecords();}
function class_MvgBC_CountRecords(){
  var oMVG = this._MvgBC;
  var icount = 0;
  with (oMVG){
    executeQuery(ForwardBackward);

    var isRecord = firstRecord();
    if (isRecord)
    {
      while (isRecord)
      {
      icount++;
      isRecord = nextRecord();
      }
    }
    firstRecord();
  }
  
  return icount;
}
function class_MvgBC_DeactivateFields()        {return this._MvgBC.deactivateFields();}
function class_MvgBC_DeleteRecord()            {return this._MvgBC.deleteRecord();}
function class_MvgBC_ExecuteQuery(v)           {return this._MvgBC.executeQuery(v);}
function class_MvgBC_FirstRecord()             {return this._MvgBC.firstRecord();}
function class_MvgBC_NextRecord()              {return this._MvgBC.nextRecord();}
//function class_MvgBC_GetAssocBusComp()         {return this._MvgBC.getAssocBusComp();}
function class_MvgBC_GetFieldValue(v)          {return this._MvgBC.getFieldValue(v) ;} //+ '';}
function class_MvgBC_GetFormattedFieldValue(v) {return this._MvgBC.getFormattedFieldValue(v) ;} //+ '';}
function class_MvgBC_GetMVGBusComp(v)          {return this._MvgBC.getMVGBusComp(v);}
function class_MvgBC_GetNamedSearch(v)         {return this._MvgBC.getNamedSearch(v) ;} //+ '';}
function class_MvgBC_GetPicklistBusComp(v)     {return this._MvgBC.getPicklistBusComp(v);}
function class_MvgBC_GetSearchExpr()           {return this._MvgBC.getSearchExpr() ;} //+ '';}
function class_MvgBC_GetSearchSpec()           {return this._MvgBC.getSearchSpec() ;} //+ '';}
function class_MvgBC_GetViewMode()             {return this._MvgBC.getViewMode() ;} //+ '';}
function class_MvgBC_InvokeMethod(){
var sMethod = arguments[0];
var aInput  = [];
  for(i=1; i<arguments.length; i++){
    aInput.push(arguments[i])
  }
  return this._MvgBC.invokeMethod(sMethod, aInput);
}
function class_MvgBC_LastRecord()              {return this._MvgBC.lastRecord();}
function class_MvgBC_NewRecord(v)              {return this._MvgBC.newRecord(v);}
function class_MvgBC_NextRecord()              {return this._MvgBC.nextRecord();}
function class_MvgBC_ParentBusComp()           {return this._MvgBC.parentBusComp();}
function class_MvgBC_Pick()                    {return this._MvgBC.pick();}
function class_MvgBC_PreviousRecord()          {return this._MvgBC.previousRecord();}
function class_MvgBC_RefineQuery()             {return this._MvgBC.refineQuery();}
function class_MvgBC_SearchExpr(v)             {return this._MvgBC.searchExpr(v);}
function class_MvgBC_SetFieldValue(n,v)        {return this._MvgBC.setFieldValue(n,v);}
function class_MvgBC_SetFormattedFieldValue(n,v)        {return this._MvgBC.setFormattedFieldValue(n,v);}
function class_MvgBC_SetSearchExpr(v)          {return this._MvgBC.setSearchExpr(v);}
function class_MvgBC_SetSearchSpec(n,v)        {return this._MvgBC.setSearchSpec(n,v);}
function class_MvgBC_SetSortSpec(v)            {return this._MvgBC.setSortExpr(v);}
function class_MvgBC_SetViewMode(v)            {return this._MvgBC.setViewMode(v);}
function class_MvgBC_UndoRecord()              {return this._MvgBC.undoRecord();}
function class_MvgBC_ViewMode(v)               {return this._MvgBC.viewMode(v);}
function class_MvgBC_WriteRecord()             {return this._MvgBC.writeRecord();}

//GetAssocBusComp() Class
class_AssBC.prototype.Name                     = class_AssBC_Name;
class_AssBC.prototype.ActivateField            = class_AssBC_ActivateField;
class_AssBC.prototype.ActivateMultipleFields   = class_AssBC_ActivateMultipleFields ;
class_AssBC.prototype.Associate                = class_AssBC_Associate;
class_AssBC.prototype.ClearToQuery             = class_AssBC_ClearToQuery;
class_AssBC.prototype.CountRecords             = class_AssBC_CountRecords;
class_AssBC.prototype.DeactivateFields         = class_AssBC_DeactivateFields;
class_AssBC.prototype.DeleteRecord            = class_AssBC_DeleteRecord;
class_AssBC.prototype.ExecuteQuery             = class_AssBC_ExecuteQuery;
class_AssBC.prototype.FirstRecord              = class_AssBC_FirstRecord;
//class_AssBC.prototype.GetAssocBusComp          = class_AssBC_GetAssocBusComp;
class_AssBC.prototype.GetFieldValue            = class_AssBC_GetFieldValue;
class_AssBC.prototype.GetFormattedFieldValue   = class_AssBC_GetFormattedFieldValue;
//class_AssBC.prototype.GetAssocBusComp          = class_AssBC_GetAssocBusComp;
class_AssBC.prototype.GetNamedSearch           = class_AssBC_GetNamedSearch;
class_AssBC.prototype.GetPicklistBusComp       = class_AssBC_GetPicklistBusComp;
class_AssBC.prototype.GetSearchExpr            = class_AssBC_GetSearchExpr;
class_AssBC.prototype.GetSearchSpec            = class_AssBC_GetSearchSpec;
class_AssBC.prototype.GetViewMode              = class_AssBC_GetViewMode;
class_AssBC.prototype.InvokeMethod             = class_AssBC_InvokeMethod;
class_AssBC.prototype.LastRecord               = class_AssBC_LastRecord;
class_AssBC.prototype.NewRecord                = class_AssBC_NewRecord;
class_AssBC.prototype.NextRecord               = class_AssBC_NextRecord;
class_AssBC.prototype.ParentBusComp            = class_AssBC_ParentBusComp;
class_AssBC.prototype.Pick                     = class_AssBC_Pick;
class_AssBC.prototype.PreviousRecord           = class_AssBC_PreviousRecord;
class_AssBC.prototype.RefineQuery              = class_AssBC_RefineQuery;
class_AssBC.prototype.SearchExpr               = class_AssBC_SearchExpr;
class_AssBC.prototype.SetFieldValue            = class_AssBC_SetFieldValue;
class_AssBC.prototype.SetFormattedFieldValue   = class_AssBC_SetFormattedFieldValue;
class_AssBC.prototype.SetSearchSpec            = class_AssBC_SetSearchSpec;
class_AssBC.prototype.SetSearchExpr            = class_AssBC_SetSearchExpr;
class_AssBC.prototype.SetSortSpec              = class_AssBC_SetSortSpec;
class_AssBC.prototype.SetViewMode              = class_AssBC_SetViewMode;
class_AssBC.prototype.UndoRecord               = class_AssBC_UndoRecord;
class_AssBC.prototype.ViewMode                 = class_AssBC_ViewMode;
class_AssBC.prototype.WriteRecord              = class_AssBC_WriteRecord;

function GetAssocBusComp()                {return new class_AssBC(this._MvgBC.getAssocBusComp());}
function class_AssBC(oAssBC)                   {if(oAssBC != null){this._AssBC = oAssBC; } }
function class_AssBC_Name()                    {return this._AssBC.name();}
function class_AssBC_ActivateField(v)          {return this._AssBC.activateField(v);}
function class_AssBC_ActivateMultipleFields(Ps){return this._AssBC.activateMultipleFields(Ps);}
function class_AssBC_Associate(i)              {return this._AssBC.associate(i);}
function class_AssBC_ClearToQuery()            {return this._AssBC.clearToQuery();}
function class_AssBC_CountRecords(){
  var oMVG = this._AssBC;
  var icount = 0;
  with (oMVG){
    executeQuery(ForwardBackward);

    var isRecord = firstRecord();
    if (isRecord)
    {
      while (isRecord)
      {
      icount++;
      isRecord = nextRecord();
      }
    }
    firstRecord();
  }
  
  return icount;
}
function class_AssBC_DeactivateFields()        {return this._AssBC.deactivateFields();}
function class_AssBC_DeleteRecord()            {return this._AssBC.deleteRecord();}
function class_AssBC_ExecuteQuery(v)           {return this._AssBC.executeQuery(v);}
function class_AssBC_FirstRecord()             {return this._AssBC.firstRecord();}
function class_AssBC_NextRecord()              {return this._AssBC.nextRecord();}
function class_AssBC_GetAssocBusComp()         {return this._AssBC.getAssocBusComp();}
function class_AssBC_GetFieldValue(v)          {return this._AssBC.getFieldValue(v) ;} //+ '';}
function class_AssBC_GetFormattedFieldValue(v) {return this._AssBC.getFormattedFieldValue(v) ;} //+ '';}
//function class_AssBC_GetAssocBusComp(v)          {return this._AssBC.GetAssocBusComp(v);}
function class_AssBC_GetNamedSearch(v)         {return this._AssBC.getNamedSearch(v) ;} //+ '';}
function class_AssBC_GetPicklistBusComp(v)     {return this._AssBC.getPicklistBusComp(v);}
function class_AssBC_GetSearchExpr()           {return this._AssBC.getSearchExpr() ;} //+ '';}
function class_AssBC_GetSearchSpec()           {return this._AssBC.getSearchSpec() ;} //+ '';}
function class_AssBC_GetViewMode()             {return this._AssBC.getViewMode() ;} //+ '';}
function class_AssBC_InvokeMethod(){
var sMethod = arguments[0];
var aInput  = [];
  for(i=1; i<arguments.length; i++){
    aInput.push(arguments[i])
  }
  return this._AssBC.invokeMethod(sMethod, aInput);
}
function class_AssBC_LastRecord()              {return this._AssBC.lastRecord();}
function class_AssBC_NewRecord(v)              {return this._AssBC.newRecord(v);}
function class_AssBC_NextRecord()              {return this._AssBC.nextRecord();}
function class_AssBC_ParentBusComp()           {return this._AssBC.parentBusComp();}
function class_AssBC_Pick()                    {return this._AssBC.pick();}
function class_AssBC_PreviousRecord()          {return this._AssBC.previousRecord();}
function class_AssBC_RefineQuery()             {return this._AssBC.refineQuery();}
function class_AssBC_SearchExpr(v)             {return this._AssBC.searchExpr(v);}
function class_AssBC_SetFieldValue(n,v)        {return this._AssBC.setFieldValue(n,v);}
function class_AssBC_SetFormattedFieldValue(n,v)        {return this._AssBC.setFormattedFieldValue(n,v);}
function class_AssBC_SetSearchExpr(v)          {return this._AssBC.setSearchExpr(v);}
function class_AssBC_SetSearchSpec(n,v)        {return this._AssBC.setSearchSpec(n,v);}
function class_AssBC_SetSortSpec(v)            {return this._AssBC.setSortSpec(v);}
function class_AssBC_SetViewMode(v)            {return this._AssBC.setViewMode(v);}
function class_AssBC_UndoRecord()              {return this._AssBC.undoRecord();}
function class_AssBC_ViewMode(v)               {return this._AssBC.viewMode(v);}
function class_AssBC_WriteRecord()             {return this._AssBC.writeRecord();}

function trace(v){
    oSysOut.println(v); 
}

Copy this into a file called "Impossible_eScript.js", which we will read, and load into our ECMA engine.
sEscriptToRun = readFile("\\Your path\\Impossible_eScript.js" );
jsEngine.eval(sEscriptToRun);//Load eScript class


The above statement imports a custom eScript like API into the ECMA engine. This is required because the JDB interface is slightly different from eScript syntax. This translation class converts eScript statements into calls that the JDB can understand.

Eg. eScript syntax is converted into Java style methods with initial lower case method names

TheApplication().NewPropertySet();//eScript
TheApplication().newPropertySet();//JDB Interface


The eScript translation class will create an abstraction layer that allows us to use conventional eScript against the JDB instance, it also implements some missing methods in the standard JDB API.

5. Run your eScript on the fly against the Server.

The pieces are comming together now, we have an ECMA engine, a Siebel Application instance, and an eScript API.

All we need to do is read in our eScript command file, which we can edit from any text editor, and run the code against the JDB connection.

String sEscriptToRun = readFile("\\Your path\\Your_eScript_commands.js");
//Your eScript to run
jsEngine.eval(sEscriptToRun);//run


This is the same code that we used to import the translation class in step 4.

When you run the program, it will connect to the server object manager, your eScript will then be converted to the proper Java methods, and run against this JDB session.

Next Steps

Thats all thats needed to create a tool that can run adhoc code on the server. The next step is to build a nice UI so users can open the script file, run it, and display results inline, or create a command line program that can be run from any editor, and return the results back to the console.

Another idea is to allow the user to "simulate" an existing business service from our tool. This tool can also be the foundations of a xUnit testing suite. A Java veteran will easily absorb this information, and make something more usable.

Conclusion

Hardcore readers will be quick to realize that this is not a complete eScript engine. It is probably 90% of an engine that will run most of your code, for example it dosnt implement the C Lib functions, and like the Business Service Simulator, it cannot run against the active UI instance. Non hardcore readers will think, this is too hard core, but I leave this out there for those who want to pick it, learn from the implementation or run with it further, but for time being, you have the basics of a very useful prototyping/testing tool.

SiebUnit - Build Inventory

This article is a continuation on SiebUnit - an xUnit implementation, and provides an inventory of the parts needed to construct a SiebUnit framework.

Like the first article, this is aimed at the Technical Architect, who understands the concept, and knows how to put the necessary pieces together to bring this idea to life.


Architecture



The architecture diagram allows us to visualize important concepts at a high level, and can be used to break up big jobs into more manageable pieces.


Rules Engine

You can replace Rules Engine with any Siebel interface that supports receiving Inputs and returning Output PropertySets. SiebUnits will also work against a standard invoked business service, or workflow.

The only caveat is that you cannot have context, that is, you must re-design your BS/WFs to decouple your logic from any specific Application context. This naturally suits the testing of a rules engine, external API such as ESB, internal APIs like custom business object libraries, and scripting utilities.


SiebUnit Framework

A SiebUnit framework can be composed of the following sub components

* Test Case Runner
* Test Case start up routines
* Test Case tear down routines (optional)
* Test case assertion
* Random data generator
* Expression language
* Expression language parser
* Expression Executor
* Expression Builder
* Test history and performance timing
* Script Library
* Business Object (BO) Library
* XML Parser
* SQL/IO Layer


Test Case Runner

This component reads the configuration data from the data store, loads all the test cases in memory, and runs each test case one by one. It records start, stop times, and logs the output, but does not make any determination on the test case result. Errors are captured, and any exceptions are suppressed, allowing the Test Case Runner to continue on until it completes all test cases.


Test Case Startup/TearDown

In order to test certain scenarios, prerequisite test data may need to be setup before the rules can be tested. This component constructs the initial test data before the test suite runs. Optionally, there can be a Tear Down component, which cleans up after all test cases.


Test Case Assertion

Assert is a computer science term used to ensure a condition is true/false to enforces application integrity. The assertion component performs the same role, it checks the outputs of each test case, and validates it against the expected result for that test case, logging the result to a history data store.


XML Parser

This component converts XML into a structured object and back. This can be used to send dynamic outbound messages, and validate inbound responses, for integration testing.

SQL/IO Layer

This component retrieves the related data from an entity for validation purposes. It is used by the Test Case Assertion component.


Random Data Generator

Even within predefined test boundaries, we can allow the generation of random data to simulate dynamic conditions.

Eg. Generate a bunch of expense activities of random type, and amount, and test it against a business rule for assignment or approval logic.

The random data generator can be used by the Setup component, or the individual test case to randomize inputs into a rule.


Expression Language

To make SiebUnits declarative, we need a way of allowing the developer to construct data, supply dynamic inputs, and validate the results of the test cases, without writing a line of code. This can be done via an Expression Language, similar to calculated field expressions in Siebel.


Expression Language Parser

This component is responsible for translating the above expressions into a form which is understandable by the Siebel eScript compiler or external engine, that will in-turn run the instructions.

The expression parser needs to differentiate between a literal and expression. This is done via 4 ways, as illustrated throughout Siebel.

1. Field Pre/Post Default

Expressions need to be prefixed with "Expr: " or a key word such as "System", "Field" etc. Anything else is considered a literal.

If you wanted to pre default another field via the square bracket notation "[Id]" this will actually result in a literal value

2. Calculated Field

A calculation is always expected to be an expression, unless it is surrounded by quotes. So the square bracket notation "[Id]" is treated as an expression in this context.

3. Field Type

This example can be seen in WFs in the Output argument of some steps. There is another field that indicates the type. Eg: Literal vs Expression.

4. Dot Notation

WFs also have another interesting expression, with the dot notation to reference child elements within property sets. This dosnt really fit into either of the above 3 categories. As it can easily be interpreted as a literal, and does not have any prefixes to indicate that it is an expression.

You have to make a call on which method you will use, and your expression parser has to recognise the above patterns if you mix the scenarios.

Eg 1. For an eScript implementation, the expression

"GetProfileAttr('Me.Id')"

Should translate this to an eScript call like this

TheApplication().GetProfileAttr("Me.Id")

The parser has to recognise that this is an expression with a literal argument.


Eg 2. The next expression provides a dynamic input.

"GetProfileAttr([&Property])"

This follows Siebels WF standard, of referencing WF properties via the ampersand prefix inside square bracket notation, requires a bit more work because you have to deal with the nesting of expressions.

This is where you can draw the line, and define styles for your expressions and have another expression to deal with that scenario

"GetProfileAttrByProperty('Property')"

Now the literal references a property that holds the value to this method. You will have to plan out your expression language in advance, and know how your parser will deal with each scenario.


Expression Executor

After you've translated the expression into the native language, it needs to be executed, and the result returned. The method that you execute the expression, will impact on the style of your expressions, and how complex your parser needs to be.

Consider implementing a short circuiting mechanism so that, if part of a condition expression is met, you can abort executing the rest of the expression.


Expression Builder

This component allows the developer to choose from a defined set of expressions, this aids in learning, reduces mistakes, and as well as documenting all the supported functions of SiebUnit. Ideally It has to integrate with the Siebel UI session, and apply the expression to the list/control.


Test History and Performance timing

This data is produced by the Assert component which validates the test case runners run history with the expected outputs, it also extracts performance timings for analysis.

Inputs and Outputs of the run are also included in the run history to help debugging. Having the results in the UI is consistent of the expectations of a declarative tool. Take note BRP engineers!

Test history and performance data can be transient, and the user can export the results, if a base line needs to be established.

Script Library and BO Library

It is important to differentiate between the above two components. A script library provides utilities, and prototypes on native objects, while a BO library provides business functions.

A SiebUnit library is tightly integrated with the script library for performing internal operations, and provides proxy functions for invoking the BO library to setup and perform test cases. When creating framework functions you will have to make conscious decisions in which library a function belongs to.


Unit Testing and Regression Testing

SiebUnit does not replace commercial regression testing suites. An important differentiation is that SiebUnits is designed to be used by developers for unit testing code/WFs before it is checked in, and to ensure application integrity is maintained before it reaches the testers.

SiebUnits are also more thorough, as only the developer could understand all the exception paths of a program. SiebUnits test technical functionality, while regression test tools test functionality.

Although SiebUnit can be used to regression test business rules, it does not test UI components. The developer is still expected to ensure that UI rules are unit tested. A badly configured link, can still bring the system down and while writing SiebUnits to create data, and check the data returned from the link could be a valid test case. It was not intended to micro manage every piece of individual configuration.

SiebUnits can offer a great sense of security for the developer, but poorly designed test cases, can also provide a false sense of security. This is normally due to poor test case coverage. Average developers will usually test the positive scenario, good developers test both positive and negative scenarios. SiebUnits require the developer to understand all the possible logic paths and design test cases to thoroughly cover off the scenarios that can result the program to fail.

Conclusion

There are numerous benefits to having SiebUnits on a project. The immediate return on investment, is that your testing period is reduced, and your application will be more robust, but the real payoff of really comes down the track, when you build highly complex systems, where one small change can break another part of the system. SiebUnits can provide your developers with confidence to make that change, without costly regression testing. SiebUnits will keep your Application, and also your sanity in check.

xUnits is a proven concept, and is a staple of any modern development platform. SiebUnit provides an evolution path, so that your developers no longer need to manually unit test. The world has JUnit , jsUnit, CUnit, OCUnit, PHPUnit, PyUnit, SUnit and now we can add SiebUnit to the list.

SiebUnit - an xUnit implementation

Introduction

Over a year ago, I presented a concept of xUnits in Siebel and how they could be used. Because it was only a concept, it was missing implementation details, so there was some doubts to whether Test driven development would work in Siebel, where code is meant to be restricted to fringe cases.

At that time, I ported some code from a back end Java project that I was working on, to automate Siebel interface verification. The idea was to pickup XML templates from a file system, insert randomize data, send it to the ESB, and validate the results.

It was used for several releases, reducing a days worth of effort to verify 50 interface interactions, to about 5 minutes. With the JUnits in place, they could be used to verify an integrated environment instantly on demand, instead of employing highly paid professionals to perform repetitive regression testing.

The tool was written in Java, didnt have a UI, it had access to the eApps SSO hashes, which meant that an evil developer could use it to log onto a server environment, and impersonate anyone they wanted. It was more of a regression testing tool than a unit test tool, and being written in Java, it was inaccessible to most of the Siebel Team.

In the spirit of a true Siebel developer, I thought a real implementation of xUnits in Siebel had to be scriptless.

So "SiebUnit" was designed to be just that, a fully declarative unit testing framework for Siebel developers.

This framework also happens to plug into a rules engine like BRP, OPA, or it could be run against a collection of business services/workflows, as long as you can pass in an Input and get an Output property set, the "SiebUnit" framework handles the rest.

This idea was sold to a Client, who immediately picked it up as a way to get developers to unit test, improve build quality, and reduce dependence on testers, and provide a structured development process.

This article and its followup, takes a step further, providing architectural and implementation concepts. It is aimed at the Technical Architect, who can grasp high level concepts, fill in the low level implementation, possibly innovate on this idea, and introduce SiebUnits into your Siebel project.


What is SiebUnit

If you had to sell the concept of xUnits in Siebel, this slide should sum it up.



This screenshot shows a SiebUnit implementation that was built for a Client.



Features

Notification System for test results

Uses a custom notification system, written in a client side library, to add persistent messages to the current view. The messages can be dismissed, by clicking the X button.


Design and Run Test Cases from the UI

Test Cases are configured, executed and debugged all within a standard Siebel UI. Debugging the rules itself, depend on the capability of the rules engine. Eg. BRP can only be debugged via log files, WFs can be debugged using the instance monitor.


Export/Import test Cases

Allow easy export and import of hierarchical test configuration data between different environments.


Trace Toggling

Toggles developer tracing, for performance testing of rules


Dynamic test data

Uses XML templates, and proxy functions to the BO Library to generate unique test data. Random data can be generated and provided on inputs, or overridden in the case of XML templates.


Expression Builder

A helper utility designed for the developer to construct the test cases declaratively. This is built using HTML, which integrates with the Siebel session.


Other

SiebUnits comes with pre built internal test cases to validate your eScript engine, and profiles ST objects vs non ST objects.

SiebUnits can be configured to run recursively to load test an environment


Case Study - Custom Rules Engine

SiebUnits was used on a project to ensure the integrity of a Rules Engine.

The design of the custom rules engine started off in small components, followed by unit testing, further build and some more unit testing. The components were then integrated, followed by another round of unit testing.

As the design got exponentially more complex, the integration of all these components, made it impractical to regression test all the exception paths, after every change.

SiebUnits was originally built with integration to BRP. So for this project, it was modified to work with this new engine.

A collection of SiebUnits was built to test the following aspects.

Declarative IF conditions
Declarative loops
Declarative procedures
Expression Translation
Expression Results
Business Service Invocations
Property Assignment
Cancel Propagation
Error Messages
Math Operations
Rule inheritance
PropertySet Interactions


Some further internal test cases were also designed to validate the behaviour of the eScript engine. This ensures that if Siebel changes the eScript engine, these internal SiebUnits will pick up on the expected outputs of dependent core functions, and allow the root cause to be addressed.

Once the SiebUnits were designed, and configured, they were used to repeatedly validate the Rules Engine after every change, to ensure previously built functionality still functioned. Changes often broke other unexpected parts of the engine, due to the tight integration and dependency between components, but with the SiebUnits in place, the report of failed test cases helped to quickly pinpoint the cause.

Towards the end of the build, a critical defect was discovered with the eScript engine, that corrupts a core object of the rules engine. This happens during random runs of the test suite, but it was easily reproducible using the load runs.

The problem required a re-design of the core object, which unfortunately touches every function in the engine. Since the new object had a different interface to the old object, it required a re-design of most of the interactions around this object as well.

The affected parts of the program was identified, and a fix was implemented in two hours. The SiebUnits test suite was run afterwards to ensure that the rules engine passed all the test cases. This provided a QA stamp on the product, and was similiar to having a team of regression testers on hand to validate your work at will.

The collection of SiebUnits also allowed load testing of the engine in a real environment. During these load runs, server resources can be monitored to ensure that they run within acceptable standards and allow capacity planning based on expected usage.

It would have been much more difficult if not impossible, to complete such a complex build without SiebUnits in place, and the regression testing and maintenance effort would have been an ongoing burden.


Intermission

Do you have BRP, OPA or a collection of Business Services that you would like to keep in check? The next article will look at the architecture of SiebUnits, and all the components that need to come together to form the framework of SiebUnit.

Note: The term Rules Engine is used broadly in this article. Even though BRP stands for Business Rule Processor, it is actually a declarative eScript replacement engine. OPA is more suited to the term for a Rules Processor, as it takes a bunch of inputs, makes a determination and returns the result. However the Rules Engine interface of Inputs/Outputs is what makes it suited to SiebUnits.

View the next article here

Quick GenBScript By fkirill

Recently, I was contacted by Siebel Master Kirill Frolov, who shared with me his new Browser Script generation tool. What I found impressive about this tool, is that it can generate browser scripts in under 5 seconds!.

This is really exciting, and I’m sure you guys/gals will think so too. The following article was written by Master Frolov, to explain the design of his program.

-----------------------------------------------

Brief Introduction

All of us, those who deal with Siebel customization in local machines, know that GenBScript generates browser scripts by parsing SRF fie and puts them into a special folder and this folder is names uniquely based on some not very obvious rules. The process itself takes (depending mostly on CPU) from 1 to 3 minutes.

If you have automatic SRF compilation during the night, you have to use GenBScript every morning when you replace the SRF.

Additionally if you have several environments (prod, prod copy for support, upcoming release test, integration test, load test etc. to name a few) and you have to switch from one environment to another throughout the day, time consumed by GenBScript may reach up to 1 – 1.5 hours.

Though incremental compilation of an applet with browser script puts browser scripts in the right directory (if you managed to set up the corresponding options in Tools), the are still a lot of cases when you need to run GenBScript by hand.

The proposed method allows quick and painless method of producing the same result as GenBScript but using slightly different approach to gain performance improvement.

Our tests show that in most cases it gives less than 10 seconds per run (mostly around 5).


Idea behind the proposal

My goal was to reproduce the result of GenBScript as close as possible so first of all I prepared the list of questions needed to be solved for that purpose:

1. How to determine the folder name to which GenBScript result is placed?

2. What’s the difference between the script sources in editor and the resulting scripts in *.js files?

The idea behind the method is to use script sources from database instead of SRF file to produce result as close as possible to original GenBScript and do it as quickly as possible.

To answer both of these questions we need to perform some analysis.

Firstly about the folder name. The folder name is constructed using two parts:

srfXXXXXXXX_YYY. For those of you who are familiar with unix or java, it should be familiar that the time is measured by counting seconds (or milliseconds) from 1st of Jan, 1970. So after some research I realized that XXXXXXXX – is the number of seconds passed from 1st of Jan 1970 when then last full or incremental compilation took place. The seconds part YYY seems to be dependent of whether the SRF is a full compile or an incremental compile.

The problem here is that XXXXXXXX not a creation or modification date of SRF file itself, but a part of an internal record in SRF file.

To approach this issue we need to remember that some time ago there was an article in one of Siebel-related forums stating that SRF file is no more than bare OLE document (which is also used by older versions of Microsoft office system) which is essentially a container format which has its own internal file system. By the way you can easily prove that opening SRF file in 7-zip archive manager which supports this format.

So after some effort I found out that the number of seconds from 1st of Jan 1970 is stored in one of two records which named… surprise-surprise… “Full Compile” and “Last Incr. Compile” respectively and are stored in the root of internal file system. Both of those records have 384 bytes in length and the seconds value is stored in bytes 4-5-6-7 (in reverse order).

So we need to parse SRF records to find one of the entries named “Full Compile” or “Last Incr. Compile”. If latter is found then we have an incremental SRF otherwise full-compiled. The second part YYY as I already mentioned depends solely on whether SRF in question is incremental or full compile. Respectively you need to use 612 for incremental and 599 for full compile.

The second question. How to organize script source code (which we have in repository database) into a bunch of script files. Well, I found out that there are quite a few changes between source code in repository and resulting script files.

1. Scripts are aggregated to parent object. This means that there is one script file per Applet/Business Service/Business Component/Application (with some minor exception for an Applet objects)

2. Names of pre-defined functions are prefixed with escaped name of a parent object

3. Applets have two script files one for controls and one for all the other functions.

I will omit all the details here, this is not really complex. You can look up source code if you wish to find them out yourself.

The implementation

The program is written in Java as single Java-file which contains numerous child subclasses.

To parse OLE file format I used apache POI library which has convenient methods to parse office documents (the funny thing is that file format is called HPSF which stands for Horrible Property Set Format :-) ).

To perform database operations I used jdbcType4 Oracle driver (jdbc6,jar from jdbc/lib folder of Oracle installation home).

Note about supported databases: in theory program should work in any database used in develpoment environment (I mean Sybase (through ODBC), MSSQL (through ODBC) and Oracle (native type 4 (thin), native type3 (TNS) or ODBC). I don’t have any idea about more exotic DB2 but I assume they are not used in development very frequently anyway. I tested only for Oracle but kept in mind other databases during development.

Testing method I used is standard text-compare. The only issue I ran into is that there is no specific order of methods in repository for browser script as they present for server scripts. This make impossible to make binary compare between GenBScript and QuickGenBScript. No other script text differences were identified.

The program was written in one working day, it is fairly small (around 700+ LoC). I and two of my colleagues use it permanently for several week already, no issues found so far.

Installation/Configuration
The program itself is run without any input parameters. This is done intentionally to promote ease of use. Based on my experience, it is easier to spend some time for initial set up than to enter some parameters each time you need to run it.

So the configuration is done through editing two text files (db.properties for database parameters and srf.properties for srf parameters). The program looks for these files in working directory (may not be the same as program directory). If you need to run QuickGenBScript for several environments it is worth doing several folder (each folder for single environment) and place both of db.properties and srf.properties in each of those folder. Then setup environment parameters for each environment and bat-files to run QuickGenBScript in those folders. Alternatively you can set up several installations of QuickGenBScript since it is quite small anyway.

What to configure.

db.properties
ConnectURL - url to database according to syntax for a given driver. Example: jdbc:odbc: for ODBC driver, jdbc:oracle:thin:@:: for Oracle thin driver.

DriverClassName - fully-qualified database driver class name. For example, sub.jdbc.odbc.JdbcOdbcDriver for an ODBC driver and oracle.jdbc.OracleDriver for oracle database driver

Login - database user login

Password - database user password

UseSchema - valid values are “true” and “false”. Whether to prefix table names with schema name (required for oracle when user name is different from siebel system user name)

SchemaName - the prefix to use for database table names. Schemas and table names are separated by dot (“.”)

RepositoryName - the name of Siebel repository to use as a source of browser script data. Normally this should be “Siebel Repository”

ShowSQL - whether to show SQL generated and some more debug output. Normally should be “false”

srf.properties
PathToSRF - path to SRF for which you need to generate browser scripts. It is used to find out the folder name where resulting scripts will be stored.

IMPORTANT: in *.properties files back-slach (“\”) must be DOUBLED. For example c:\Siebel\Client\PUBLIC\ENU\Siebel_SIA.srf transforms to c:\\Siebel\\Client\\PUBLIC\\ENU\\Siebel_SIA.srf

FullCompileSuffix=599. You need to run original GenBScript againts a full-compile SRF file and find out which is the suffix for directory where it puts generated files. In version 8.0 this is 599.

IncrementCompileSuffix=612. The same for increment SRF.

TargetDir - the root public\enu directory where generated files should be stored.

IMPORTANT: IMPORTANT: in *.properties files back-slach (“\”) must be DOUBLED. c:\Siebel\Client\PUBLIC\ENU turns to c:\\Siebel\\Client\\PUBLIC\\ENU

TargetEncoding=utf-8. Target encoding for generated scripts. Leave it as it is.

How to run
Prerequisites: JRE or JDK version 1.6. I assume java.exe can be found in PATH, so no java path is present in run.bat

run.bat contains a single line code which essentially includes apache POI, database library (jdbc6.jar in distributive) and QuickGenBScript.jar which contains all the class.files from QuickGenBScripts (quite a few).

Unfortunately initial implementation requires quite a lot of RAM to run (300M is enough). This is due to the fact that SRF file contains > 70 000 entries (in our case) which need to be parsed.

Additionally I published Eclipse project containing all that is required to run and debug QuickGenBScript in Eclipse environment.

PS: Sorry guys. almost no comments in source code, I’m just really very busy nowadays with my full-time job. I’m a bit ashamed to produce code without comments, but... That’s life. Besides, I’m willing too much to share this interesting research project with you.

Screenshots
With option ShowSQL=true


With option ShowSQL=false


References:
SourceForge download page:
https://sourceforge.net/projects/quickgenbscript/files/

Source code can be obtained via SVN
svn co https://quickgenbscript.svn.sourceforge.net/svnroot/quickgenbscript quickgenbscript

Official notice: QuickGenBScript is purely for development tool, use it on your own risk. Though it’s working, it is experimental and completely unsupported by Oracle. NEVER USE IT IN PRODUCTION ENVIRONMENT!!! Nevertheless, I believe many of you will like it.
-----------------------------------------------

Thanks to Kirill, for providing the community with greater insight into the inner workings of Siebel, and improving the lives of all Developers.

I think there is one aspect that would really top this off. How do we automatically discover the suffix code for Full and Incremental compiles? I think the answer is in the SRF, consider it a challenge =).

Ultra browser script generator 2

A fellow colleague had a chance to test my new Ultra GenB script progress screen enhancement, and the feedback was:

"Every developer should have this"

If you dont know what the excitement is all about, a while ago i presented the Ultra GenB script generator, a free utility to ease the pains of generating browser scripts. This time i've come back with an enhancement to the original program.

The time need to generate browser scripts varies, depending a lot of factors such as the amount of browser scripts you have in your application, the amount of memory/ disk space/CPU utilization currently being used, and Genbscript.exe also runs faster off a fully compiled SRF, compared to incremental compiles, so you could be waiting 1-10 minutes for the program to finish not knowing what Siebel is doing in the background.

My browser script generator fills this user experience gap, by providing a visual cues on the progress of genbscript.exe.

[Features]

All the wonderful features of original +
1) Displays the name of the file currently being generated
2) Displays how many files have been generated so far
3) Turns Red to signal failure or user action
4) Turns Green to signal success





Note: Feature 1, works by polling the file system, so the results are not real time, hence your experience will vary depending on how fast or slow your system is running.

The file and source code can be downloaded freely from my LinkedIn Group: Impossible Siebel