Data Spaces - Shareable Data Objects for Tests

Need for Spaces in Test Automation

Test resources are in general a great way for setting up pre-reqiusities and providing required resources to tests.

However, there are circumstances where you need to create and share data among tests. For example:

  • You want to setup a workflow (let’s say 4 tests representing CRUD operations on object) where data setup by one test is then utlilized by the subsequent tests.

  • You want to maintain a data structure that contains consumable data.

Types of Spaces

The space object is accesed using the request object that is passed to every test resource function as well as test function in Arjuna.

There are three types of test spaces that are provided to you:

Group Space

Within a @for_group test resource function, the following Python code refers to Group space:

# Implicitly
request.space

# Explicitly
request.group.space

In other test resource functions or test functions, it must be explictly accessed:

# Explicitly
request.group.space

Module Space

Within a @for_module test resource function, the following Python code refers to Module space:

# Implicitly
request.space

# Explicitly
request.module.space

In other test resource functions or test functions, it must be explictly accessed:

# Explicitly
request.module.space

Note

Module space is not accessible from a group resource function as it is at a lower level than group.

Test Space

Within a @for_test test resource function or a test function, the following Python code refers to Test space:

request.space

Note

Test space is not accessible anywhere else as it is the lowest space level.

Defining and Utilizing Objects in Spaces

Declaring and accessing an object in any space is done as if you are dealing with a defined attribute of an object:

# Defining
request.space.obj_name = some_value

# Retrieving
request.space.obj_name

# Re-Assignment
request.space.obj_name = new_obj

# Modification (assume a dictionary)
request.space.obj_name[2] = 4
del request.space.obj_name[5]

Test Space

This is the simplest to understand space and you will mostly utilize it to create objects in @for_test test resource functions which can then be used by test functions.

Definition

@for_test
def tres(request):
    request.space.immutable = "testing"
    request.space.mutable = {1:2, 3:4}
    yield

Access in Test Function

@test
def check_test_space(request, tres):
    assert request.space.immutable == "testing"
    assert request.space.mutable[3] == 4

Access in @for_test Resource Function

@for_test
def another_tres(request, tres):
    assert request.space.immutable == "testing"
    assert request.space.mutable[3] == 4
    yield

Test Space is NOT Shared Among Tests

Test Space is unique to a test and is not shareable.

The following works perfectly as you are modifying the object within a test:

@test
def check_test_space_modify(request, tres):
    request.space.immutable = "changed"
    request.space.mutable[5] = 6
    assert request.space.immutable == "changed"
    assert request.space.mutable[5] == 6

However, if you expect these changes to reflect in next test(s) in the run sequence, it will not work. Each test gets its own copy of the objects in Test Space:

@test
def check_test_space_test(request, tres):
    assert request.space.immutable == "changed" # Fails
    assert request.space.mutable[5] == 6 # Will Fail if above is commented.

Modifying Space Objects in **Multiple Test Resources for a Test**

When you use multiple test resource functions for a given test, then its space is defined by all definitions and modifications done by these resource functions.

@for_test
def tres_multi_1(request):
    request.space.something = "test"
    yield

@for_test
def tres_multi_2(request):
    assert request.space.something == "test"
    request.space.something = "changed"
    yield

@test
def check_space_multi_res(request, tres_multi_1, tres_multi_2):
    assert request.space.something == "changed"

Same is true if you are using the resource functions as a chain:

@for_test
def tres_chain_1(request):
    request.space.something = "test"
    yield

@for_test
def tres_chain_2(request, tres_chain_1):
    assert request.space.something == "test"
    request.space.something = "changed"
    yield

@test
def check_space_chain(request, tres_chain_2):
    assert request.space.something == "changed"

Module Space

The workings of Module space are similar to those of test space.

Definition

@for_module
def mres(request):
    request.space.immutable = "testing" # Can use request.module.space as well.
    request.space.mutable = {1:2, 3:4}
    yield

Accessing Module Space in Test Function (Explicit)

@test
def check_mod_space(request, mres):
    assert request.module.space.immutable == "testing"
    assert request.module.space.mutable[3] == 4

Cross-Space Lookup in Arjuna (Test -> Module)

If the named object that you want to find does not exist in Test Space, Arjuna automatically looks for it in Module Space and then in Group Space.

To use this lookup feature, rather than explicit lookup in a particular space, you should let Arjuna handle it.

This means instead of using the following in test function

request.module.space.x

if you use

request.space.x

it triggers the automatic lookup of Arjuna across spaces.

See the following section for this implicit lookup.

Accessing Module Space in Test Function (Implicit)

@test
def check_mod_space(request, mres):
    # Looks in Test Space and then in Module Space.
    assert request.space.immutable == "testing"
    assert request.space.mutable[3] == 4

The above code works as Arjuna after not finding these objects in Test Space will automatically look for them in Module Space.

Access in @for_module and @for_test Resource Functions

The Module Space can be accessed in other module level as well as test resource functions:

@for_module
def another_mres(request, mres):
    # Directly looks in Module Space
    assert request.space.immutable == "testing"
    assert request.space.mutable[3] == 4
    yield

@for_test
def tres(request, mres):
    # Looks in Test Space and then in Module Space.
    assert request.space.immutable == "testing"
    assert request.space.mutable[3] == 4
    yield

Module Space is Shared Among Tests in SAME Module

Unlike the Test Space, Module Space is shared among tests in a module.

It means modifications done by one test are seen by another:

@test
def check_mod_space_modify(request, mres):
    request.module.space.immutable = "changed"
    request.module.space.mutable[5] = 6
    assert request.space.immutable == "changed"
    assert request.space.mutable[5] == 6

# Test in same module, executed subsequently
@test
def check_mod_space_test(request, mres):
    assert request.space.immutable == "changed"
    assert request.space.mutable[5] == 6

Creating and Sharing Data From Within Tests in a Module

As the Module Space is shared among tests, you can create new objects in this space in a test function as well. These can then be accessed and/or modified in subsequent tests in the module.

In the following code created_id is defined in first test function and then accessed in the subsequent ones.

@test
def check_crud_add(request):
    # Some object addition code followed by
    request.module.space.created_id = "abc123"

@test
def check_crud_edit(request):
    cid = request.space.created_id
    # Code to edit object for this id
    assert cid == "abc123"

@test
def check_crud_delete(request):
    cid = request.space.created_id
    # Code to delete object for this id
    assert cid == "abc123"

Group Space

The workings of Group space are similar to those of module space.

Definition

@for_group
def gres(request):
    request.space.immutable = "testing" # Can use request.group.space as well.
    request.space.mutable = {1:2, 3:4}
    yield

Accessing Group Space in Test Function (Explicit)

@test
def check_group_space(request, gres):
    assert request.group.space.immutable == "testing"
    assert request.group.space.mutable[3] == 4

Cross-Space Lookup in Arjuna (Test -> Module -> Group)**

If the named object that you want to find does not exist in Test Space, Arjuna automatically looks for it in Module Space and then in Group Space.

To use this lookup feature, rather than explicit lookup in a particular space, you should let Arjuna handle it.

This means instead of using the following in test function

request.group.space.x

if you use

request.space.x

it triggers the automatic lookup of Arjuna across spaces.

See the following section for this implicit lookup.

Accessing Group Space in Test Function (Implicit)

@test
def check_group_space(request, gres):
    # Looks in Test Space, then in Module Space and then in Group Space
    assert request.space.immutable == "testing"
    assert request.space.mutable[3] == 4

The above code works as Arjuna after not finding these objects in Test Space will automatically look for them in Group Space.

Access in @for_group, @for_module and @for_test Resource Functions

The Group Space can be accessed in all other resource functions:

@for_group
def another_gres(request, gres):
    # Directly looks in Group Space
    assert request.space.immutable == "testing"
    assert request.space.mutable[3] == 4
    yield

@for_module
def mres(request, mres):
    # Looks in Module Space and then in Group Space
    assert request.space.immutable == "testing"
    assert request.space.mutable[3] == 4
    yield

@for_test
def tres(request, mres):
    # Looks in Test Space, then in Module Space and then in Group Space
    assert request.space.immutable == "testing"
    assert request.space.mutable[3] == 4
    yield

Group Space is Shared Among Tests Across Modules

Unlike the Test Space, Group Space is shared among tests.

Unlike the Module Space, Group Space is shared among tests in across modules.

It means modifications done by one test are seen by another:

@test
def check_group_space_modify(request, mres):
    request.module.space.immutable = "changed"
    request.module.space.mutable[5] = 6
    assert request.space.immutable == "changed"
    assert request.space.mutable[5] == 6

# Test in same or different module, executed subsequently
@test
def check_group_space_test(request, mres):
    assert request.space.immutable == "changed"
    assert request.space.mutable[5] == 6

Creating and Sharing Data From Within Tests Across Modules

As the Group Space is shared among tests across modules, you can create new objects in this space in a test function as well. These can then be accessed and/or modified in subsequent tests in any other module.

In the following code created_id is defined in first test function and then accessed in the subsequent ones.

@test
def check_crud_add(request):
    # Some object addition code followed by
    request.group.space.created_id = "abc123"

# Test in same or different module, executed subsequently
@test
def check_crud_edit(request):
    cid = request.space.created_id
    # Code to edit object for this id
    assert cid == "abc123"

# Test in same or different module, executed subsequently
@test
def check_crud_delete(request):
    cid = request.space.created_id
    # Code to delete object for this id
    assert cid == "abc123"