Use of Set Global Variable

2,275 views
Skip to first unread message

Chuck Lutz

unread,
Nov 17, 2018, 11:30:50 AM11/17/18
to robotframework-users
Hi folks,

I find it is easy to go in circles with RF - something that seemed to work a while ago suddenly doesn't anymore and I have to revisit basic concepts. It is a bit difficult to keep in mind some basic principles so that they "stick"...

RF is very powerful, but the documentation examples tend to treat isolated aspects. To help our internal users get familiar more quickly, I am working on an internal "user guide" in which I am trying to put together a few more integrated examples, to show at once how many aspects are used together.

We have a need to share some global data across many hierarchical test suites. I am trying to make a simple example of use of Set Global Variable, but it is not working as I had hoped. I've used this approach successfully before, I believe, so I am confused as to what I'm doing wrong this time.

Part of this experiment was to explore the meaning of the sentence "If you need to share variables or keywords, you can put them into resource files that can be imported both by initialization and test case files." in the User Guide. I was interpreting "share" as to mean "make globally available" but that is not how it worked.

Here is the structure of my example:

shared/
    common.robot
test_shrd_var2/
    __init__robot
    tests/
        tests1.robot
        tests2.robot


common.robot:

*** Variables ***
${counter}    ${1}

*** Keywords ***
Increment Counter and Report Result
    [Arguments]     ${ctr}
    ${ctr} =    Set Variable    ${ctr + 1}  # 'Set Variable   ${ctr}  +  ${1}' didn't work
    [Return]   ${ctr}



__init__.robot:

*** Settings ***
Resource      ../shared/common.robot
Suite Setup   Tests Setup

*** Keywords ***
Tests Setup
    Set Global Variable   ${glb_ctr}    ${counter}



tests1.robot / tests2.robot:

*** Settings ***
Resource    ../../shared/common.robot

*** Test Cases ***
Increment Counter
    ${glb_ctr} =    Increment Counter and Report Result    ${glb_ctr}


The two .robot files are identical and the intent is to just have them run in a sequence and modify the simple global variable.

When I realized that just including "common.robot" in both .robot files in fact did not make the ${counter} variable available "globally" to both ("share"), I introduced the __init__.robot and tried to use the Set Global Variable in there to create a new ${glb_ctr} using the ${counter} from the common.robot shared file.

I would have expected the ${glb_ctr} variable to go from values 1 to 2 in tests1.robot and then from 2 to 3 in tests2.robot, but instead each just goes from 1 to 2. It seems that ${glb_ctr} is not really global?

Thanks in advance for any advice,
Chuck

Anne kootstra

unread,
Nov 17, 2018, 5:24:38 PM11/17/18
to robotframework-users
In your explanation you're concept of Global Scope does not align with the documentation and what you're observing. This causes the difference in behaviour and expectation. 

So if we were to explain it in simple terms what you should do with variables in a particular Variable Scope would be this: 

Global: Communicate values between Suites (Robot Files) using Set Global Variable or command line assignment.
Suite:   Communicate values between Test Cases in a single Suite File using Variable Table and Set Suite Variable Keywords.
Test:    Communicate values between Keywords in a single Test Case using Set Test Variable or Value assignment using Keywords in a Test Case.
Local:  Store and Manipulate values in a single Keyword using Set Variable or Value Assignment using other Keywords.

It would have made life a lot simpler if a variable created in a particular scope would be exclusive in all scopes but in Robot Framework this isn't the case. The same variable name can be used in every scope at the same time and hold a different value at the same time. To manage this Robot Framework looks at the lowest scope of the available variable and returns it. So a variable used in a keyword will vanish when returning back to a Test Case. The same happens with Test Case scoped variables when moving on to the next Test Case. This will then make the value in that scope become returned again.

Putting this in a practical context: 
  1. All: Do not reuse variable names from other scopes. This makes making sense of your variables in even simple scripts much more difficult.
  2. Use Global Scope variables as Constants and only assign them through command line arguments. Communicating variable updates from Suite to Suite should be avoided as Suites should be atomic and not rely on the state of the previous Suite. Use ALL CAPS when naming these variables.
  3. Use Suite Scope variables if you plan on sharing values between Test Cases. The practice of having self contained test cases is quite common as it helps to build more stable suites. So use the keywords for creating them rarely. Contrary to the guide I'd recommend using variable names in CamelCase as it makes distinguishing them difficult from Globals. 
  4. The Test Case Scope is often not used for communicating between keywords through a unified Scope. Most will prefer to input values to keywords through arguments. For this reason I'd consider this to be a special Local variable. My advise for naming is full lowercase with _ for separation of words.
  5. Local. The variables created in the context of a keyword and are cleaned up after the keyword ran.  Naming should be single string, all lowercase. 
With the above in mind the different files: __init__, Suite, Resource, Variable will just be a mix of scopes depending upon the different Set *** Variable keywords used.

Kind regards,

Anne


Op zaterdag 17 november 2018 17:30:50 UTC+1 schreef Chuck Lutz:

Chuck Lutz

unread,
Nov 18, 2018, 8:37:35 AM11/18/18
to robotframework-users
Hi Anne,

Thanks for the explanation - I have read it two or three times and will probably have to do that some more :-) I haven't yet figured out how to take it to fix my example. I would like to take this advice and turn it into a more explicit example that uses a hierarchy of test suites - I will try to do that and put it back here.

Actually, we are exactly trying to use a global variable as a read-only. In fact, we are already doing that in our real test suite - but that is currently located on another system that I don't have access to at the moment, so I was trying to recreate a basic example on a Linux VM on my local laptop. I will compare my failed attempt below to what I am doing on the other system when I have a chance. 

I agree with the general philosophy that the tests should be independent. The reason for the need for the global shared variable is performance - we are running RF post-mortem on some data that is written out in HDF5 format. We are accessing the HDF5 data through Pandas and have written a simple API of sorts in custom RF keywords that in turn access the Pandas DataFrames. So this top-level variable is a dictionary of Pandas DataFrames, that is used by all of the independent test suites (it is created at the beginning of the test run, by a top-level suite initialization-file Suite Setup keyword, using the HDF5 reading facilities in Pandas). If each test case / test suite had to re-read the HDF5 data it would slow everything down to the point of being unusable.

This is why I was confused by my failure in the example in my original post - I am already doing something like this in the real test suite. So I will figure out the difference and report back when I can.

Thanks,
Chuck

Pekka Klärck

unread,
Nov 18, 2018, 9:17:50 AM11/18/18
to charles...@gmail.com, robotframework-users
Using `Set Global Variable` sets a value to a variable in the global scope, but it doesn't change the variable so that setting it again using other means would also change the global value. You need to use `Set Global Variable` again if you want to change the global value.

Sent from my mobile.

--
You received this message because you are subscribed to the Google Groups "robotframework-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to robotframework-u...@googlegroups.com.
To post to this group, send email to robotframe...@googlegroups.com.
Visit this group at https://groups.google.com/group/robotframework-users.
For more options, visit https://groups.google.com/d/optout.

Anne kootstra

unread,
Nov 18, 2018, 4:14:51 PM11/18/18
to robotframework-users
The below example sets up the counter in the init file and in the test cases the counter is incremented. All of the counter keywords are shared through a resource file. 

__init.robot__
*** Settings ***
Resource    ../shared/common.robot
Suite Setup   Suite Setup


*** Keywords ***
Suite Setup
   
Setup Counters Global
   
Create Counter    Global
   
Setup Counters Global
    $
{dic}    Create Dictionary
   
Set Global Variable   ${COUNTERS}    ${dic}

common.robot
*** Settings ***
Library    Collections


*** Keywords ***
   
Create Counter
   
[Arguments]    ${name}    ${start}=${0}    ${increment}=${1}  
   
Dictionary Should Not Contain Key    ${COUNTERS}    ${name}    The counter name: "${name} is already in use.
   
    ${counter}    Create Dictionary    start=${start}    value=${start}    increment=${increment}
    Set To Dictionary    ${COUNTERS}    ${name}    ${counter}
    [Return]    ${counter}


Get Counter
    [Arguments]    ${name}
    Dictionary Should Contain Key    ${COUNTERS}    ${name}    No counter found with name: "
${name}".
    ${counter}    Get From Dictionary    ${COUNTERS}    ${name}
    [Return]    ${counter}    


Get Counter Value
    [Arguments]    ${name}
    ${counter}    Get Counter    ${name}
    [Return]    ${counter.value}
   
Increment Counter
    [Arguments]    ${name}    ${value}=None
    ${counter}    Get Counter    ${name}
   
    ${increment}    Set Variable If    
    ...                "
${value}"=="None"    ${counter.increment}
    ...                "
${value}"<>"None"    ${value}
   
    Set To Dictionary    ${counter}    value    ${counter.value + ${increment}}
    Set To Dictionary    ${COUNTERS}   ${name}    ${counter}
    [Return]    ${counter.value}

test1.robot
*** Settings ***
Resource    ../../shared/common.robot


*** Test Cases ***
Increment Counter

    $
{val}    Get Counter Value    Global
   
Should Be Equal As Integers    ${val}    ${0}
   
    $
{glb_ctr} =    Increment Counter    Global    ${5}
   
Should Be Equal As Integers     ${glb_ctr}    ${5}
   

test2.robot
*** Settings ***
Resource    ../../shared/common.robot


*** Test Cases ***
Increment Counter

    $
{val}    Get Counter Value    Global
   
Should Be Equal As Integers    ${val}    ${5}
       
    $
{glb_ctr} =    Increment Counter    Global
   
Should Be Equal As Integers    ${glb_ctr}    ${6}

If you look closely, then only in the __init__.robot the keyword Set Global Variable is used for creating the global variable. Because I don't use the name COUNTERS to create another variable, the standard keyword for modifying a dictionary will pick the variable in the Global scope and update it there. Whether this is intended or accidental behaviour is something I'm not sure about but would be easily fixed with some additional lines in the common.robot keywords for reading the entire COUNTERS into a local variable, then updating it and then replacing the new one in the Global scope. 

Using __init__ as a preprocessor seems like a good approach. With that said, I do think that any Global variables created should not be modified by the Test Cases but instead use it as an immutable reference. 

Kind regards,

Anne

Op zondag 18 november 2018 14:37:35 UTC+1 schreef Chuck Lutz:

Pekka Klärck

unread,
Nov 19, 2018, 3:01:31 AM11/19/18
to koot...@gmail.com, robotframework-users
su 18. marrask. 2018 klo 23.14 Anne kootstra (koot...@gmail.com) kirjoitti:
>
> If you look closely, then only in the __init__.robot the keyword Set Global Variable is used for
> creating the global variable. Because I don't use the name COUNTERS to create another variable,
> the standard keyword for modifying a dictionary will pick the variable in the Global scope and update
> it there. Whether this is intended or accidental behaviour is something I'm not sure

This is by design. In programming it is generally different to assign
a variable and to change the state of an object that a variable points
to. When you modify the state, the object stays the same and changes
are seen by everyone who sees the object. If you set or reset a
variable, who sees the change depends on the scope you use. Example
below illustrates this in the Python interpreter:

>>> x = [1, 2, 3]
>>> def f1():
... x.append(4)
... print(x)
...
>>> def f2():
... x = 'local value'
... print(x)
...
>>> f1()
[1, 2, 3, 4]
>>> f2()
local value
>>> print(x)
[1, 2, 3, 4]
>>>


Cheers,
.peke
--
Agile Tester/Developer/Consultant :: http://eliga.fi
Lead Developer of Robot Framework :: http://robotframework.org

Anne kootstra

unread,
Nov 19, 2018, 5:22:04 AM11/19/18
to robotframework-users
Thanks for clearing this up. 

Op maandag 19 november 2018 09:01:31 UTC+1 schreef Pekka Klärck:
Reply all
Reply to author
Forward
0 new messages