Map vs metaclass - any reason to select one over the other?

So in pursuing ways to allow the sharing of variable values between one test case to another within a Test Suite, my colleagues and I have found two different methodologies and are wondering if there is any specific advantage of one over the other.

The first method tried would be using a metaclass, where we have a Keyword method setup for instantiating the class within the “Common_Functions” Keyword:

	public void create_global_variable(String name, def value) {
		GroovyShell shell = new GroovyShell()
		MetaClass mc = shell.evaluate("internal.GlobalVariable").metaClass
		String getterName = "get" + name.capitalize()
		mc.'static'."$getterName" = { -> return value }
		mc.'static'."$name" = value
	}

and it’s corresponding usage in the Test Suite :

def setupTestCase() {
	KeywordUtil log = new KeywordUtil()
	Common_Functions sites = new Common_Functions()
	
	//Establish Run Variables
	String user_login_id = 'myID'
	String site_name = 'mySite'
	
	//Establish Global Variables
	sites.create_global_variable('site_status', 'not set')
	sites.create_global_variable('user_login', user_login_id)
	sites.create_global_variable('current_site', site_name)
		
	//Perform Login Action
	Login user_login = new Login()
	user_login.login(GlobalVariable.metaClass.getProperty(GlobalVariable, 'user_login'), System.getenv('My_Password'))
	
	Navigation menu = new Navigation()
	String site_set = menu.select_site( GlobalVariable.metaClass.getProperty(GlobalVariable, 'current_site'))
	sites.create_global_variable('site_status', 'site_set')	
}

On the other hand we have a Map variable in Keyword Shared_Vars:

Map glblvars = ['site_name':'','user_name':'','object_timeout':'', 'site_status':'']

and its setup logic:

def setupTestCase() {

	Shared_Vars myvars = new Shared_Vars()
	KeywordUtil log = new KeywordUtil()
	
	//Establish Global Variables
	myvars.glblvars.put('site_name', 'MySite')
	myvars.glblvars.put('user_name', 'MyUserName')
	myvars.glblvars.put('site_status', 'not set')
		
	//Perform Login Action
	Login user_login = new Login()
	user_login.login(myvars.glblvars.get('user_name'), System.getenv('MyPass'))
	
	Navigation menu = new Navigation()
	String site_set = menu.select_site(myvars.glblvars.get('site_name'))
	myvars.glblvars.put('Global site_status 2: ', site_set)
	log.logInfo("site_status: " + myvars.glblvars.get('site_status'))
	
}

So the question is if - other than personal preference - there would be a fundamental reason to prefer a specific methodology?

In both cases the understanding is that the respective Keyword would need to be present in all leveraging functions, and that relatively speaking the difference in coding is fairly miniscule.

I personally find no reason to prefer one of them.

I think that some condition or context is given, the two may have different applicability. Without any constraint, they might be similar.


Rather I wonder why you do not use GlobalVariable of type Map.

You can use it in a trivial Test Case, like this:

import internal.GlobalVariable as GlobalVariable

GlobalVariable.glblvars['site_name'] = 'MySite'
GlobalVariable.glblvars['user_name'] = 'MyUserName'
GlobalVariable.glblvars['site_status'] = 'not set'

for (String key in GlobalVariable.glblvars.keySet()) {
	String value = GlobalVariable.glblvars[key]
	println "key=" + key + ", value=" + value 
}
2023-05-23 21:35:54.784 INFO  c.k.katalon.core.main.TestCaseExecutor   - --------------------
2023-05-23 21:35:54.787 INFO  c.k.katalon.core.main.TestCaseExecutor   - START Test Cases/TCx
key=site_name, value=MySite
key=user_name, value=MyUserName
key=site_status, value=not set
2023-05-23 21:35:55.634 INFO  c.k.katalon.core.main.TestCaseExecutor   - END Test Cases/TCx

Your Shared_Vars instance looks the same as the GlobalVariable variable of Map to me.

Personally I have my own project

I wanted to switch Execution Profiles during a Test Case run. Of course I created Execution Profiles using the usual Katalon Studio’s GUI. But, as you know, Katalon Studio does not allow loading Execution Profiles dynamically. So I developed the “ExecutionProfilesLoader”.

In this project, I used the Groovy’s metaClass to extend the GlobalVariable object; to load an Execution Profile and add global variables on the fly. In order to implement this processing, metaClass was necessary; no other choice.

The idea is to create them programmatically on the fly so that we can more easily leverage them in CI/CD processes, plus it means we don’t have to add the items in each of our dozen+ profiles every time we need to create a new variable

JSON would be easier format to mange programattically.

Is there any way to directly modify the Data Binding settings for the entire test suite instead of having to use the GlobalVariables method?

Use Case - we have a test script where we need to perform a login, and based on the data presented to the user at login time potentially change the data binding values for every subsequent test case.

We could use GlobalVariables to do this, but it’s quite messy and means that (since the test cases may need to be run individuallly as well) we have to put a significant amount of code logic into each script to see if the GlobalVariable is populated and (if so) override the test script’s individualized variable value.

If there was a way to directly update the variable binding for the entire test suite at the outset it would give us a way to avoid a lot of extraneous code, is it possible to directly change the values bound to a suite variable while that suite is running?

Perhaps. But I wouldn’t go that route.

Create your vars/structure externally in a json file. At suite startup (@BeforeTestSuite), read the structure in to a map. Ad hoc overrides can be placed in Test Case properties (again in json format). Whether you copy them to GlobalVariables is a matter of choice.

Benefit: the json file can be produced by hand or generated by any system in the toolchain.

As for your metaclass implementation – clever but seems a little over engineered when you consider the key benefit of mataprogramming is the ultimate in re-usability and extensibility. This is test code, not production. Just my opinion.

i dont see what you mean. if you could show us a messy code which uses GlobalVariable, possibly i could understand you .

Thanks for both your replies!

@kazurayam - by “messy code” what I’m dealing with is that these cases can be run two ways - either via a test suite or directly.

If I’m running locally then I want to use the variable bindings applied to the individual test case, if we’re running as part of a test suite then we want to leverage an alternate methodology due to the fact that many of our test suites have 30+ test cases and don’t want to have to perform manual variable binding to each and every test case in a suite, across tens of suites.

Where this gets messy then is that we have to then start including code into each script to account for this, i.e. - check to see if a global variable is populated and, if so, then use the global variable value throughout the script and if not then use the variables from the test script itself.
The more that we dig into this I’m thinking of an alternate method, which I’m going to ask about in a separate thread since it’s a different topic :slight_smile:

see Method for retrieving an array-based list of all testIDs within a given folder structure in Katalon Studio? - #9 by kazurayam for my proposal