Data Generation and Data Entities

Random Data Generation

Data Generation is a common need in testing and test automation.

Python’s own libaries can be used for generation of random strings and numbers. However, tester’s needs are much more involved than that.

Arjuna currently has basic support for contextual data generation by using mimesis library provided by its Random class.

Using its methods you can generate the following:

  • first_name
  • last_name
  • name
  • city
  • country
  • email
  • phone
  • house_number
  • street)name
  • street_number
  • postal code
  • sentence
  • ustr
  • fixed_length_number

Creating a Basic Data Entity

Arjuna’s data_entity gives you an advanced, yet easy way of creating entities that contain associated generated/provided data.

In simple words, a Data Entity is a Python class whose objects when created can contain automatically generated associated data.

In its simplest form, data_entity is a Python class (without custom behaviors/methods) creator without writing a class. In its more involved forms, it starts serving complex data needs of today’s automation world.

In the rest of this page, more advanced options will be considered. Here’s a simple example of a Data Entity with static data:

# Provide name of entity and names of desired data attributes as strings
Person = data_entity("Person", "name", "age", "country")

# Or provide names of data attributes as a single string (space separated)
Person = data_entity("Person", "name age country")

# Now you can use it as a regular Python class as if the data attributes are keyword arguments in class definition.
person = Person(name="Ravi", age=25, country="India")

# Access Data
person.name
person.age
person.country

This might look fancy to the novice, however so far there is nothing special about it. You could have achieved the above by using namedtuple which is available in Python’s built-in collections module.

Next sections tell you what makes data_entity special.

Setting Defaults in Data Entity

You can define data attributes with default value in the familiar Pythonic way for keyword arguments:

Person = data_entity("Person", "name age", country='India')

Basic Usage of Random with Data Entity

You can club the usage of Random class with Data Entity to create an object with random data:

Person = data_entity("Person", "name age country")
person = Person(name=Random.name, age=Random.int(begin=18, end=65), country=Random.country)

Dynamic Generation of Data for Data Entities

Using Callables in Random Class

This is the point where the true power of Data Entities starts to unfold.

You can associate a Data Entity’s attribute with a callable to generate unqiue data for each object of this Data Entity.

Person = data_entity("Person", "name age", country=Random.country)

# Gets assigned a random country when object is created
person1 = Person(name=Random.name, age=Random.int(end=65))

# Gets assigned a random country when object is created
person2 = Person(name=Random.name, age=Random.int(end=65))

Using User-Defined Callables

You can also use your own random data generator callables:

def some_data_gen():
    return random.randint(20,60)

Person = data_entity("Person", "name country", age=some_data_gen)

# Gets assigned a random int as age when object is created, as returned by some_data_gen
person1 = Person(name=Random.name, country='India')

# Gets assigned a random int as age when object is created as returned by some_data_gen
person2 = Person(name=Random.name, country='India')

Using generator Construct to Provide Arbitrary Arguments to Generator Callables

The data generator functions could take any positional arguments and/or keyword arguments.

Data Entities accept Arjuna’s generator construct to support this advanced facility.

You can use it with your own functions as well. Here’s an example with Random.int function:

Person = data_entity("Person", "name country", age=generator(Random.int, begin=18, end=65))

Processing Dynamically Generated Data

Basic Processor Callable

You might want to process the generated data before making it a part of Data Entity. You can do it by passing a converter callable to generator:

def lower(in_str):
    return in_str.lower()

Person = data_entity("Person", "age country", name=generator(Random.name, processor=lower))

Here if the generated name is “Ravi Sharma”, it will stored as “ravi sharma” in the data entity post conversion.

Processor Callable as a Method of Generated Data Object

If the processor is a string, it is assumed to be a method of the generated data object and called:

Person = data_entity("Person", "age country", name=generator(Random.name, processor="lower"))

Defining Processor Callable with Arbitrary Arguments

The generator constructs also accepts Arjuna’s processor construct for advanced usage:

def replace_space(in_str, char=":"):
    return in_str.replace(" ", char)

processor = processor(replace_space, char="-")
Person = data_entity("Person", "age country", name=generator(Random.name, processor=processor))

If the callable provided to processor is a string, it is assumed to be a method of the generated data object and called.

Defining Composite Data Using composite and composer Constructs

At times, you might want to club data obtained from multiple generators. You might want to combine some static data with it as well, as needed.

Data Entities in Arjuna accept Arjuna’s composite construct for data attributes.

Once the data is available as a single sequence, it is composed together using the composer callable that you can optionally provide, else the same sequence is stored as the value for this data attribute.

If you have reached this stage, it is assumed, that you know what you are doing. So, here’s a complete example demonstrating everything a Data Entity has to offer:

def to_upper_case(data_str):
    return data_str.upper()

def join(in_list, char=":"):
    return char.join(in_list)

processor = processor(replace_space, char="-")
Person = data_entity("Person",
        age = generator(Random.int, begin=18, end=65),
        country = Random.country,
        name=composite(
                "Mz",
                generator(Random.first_name, processor="upper"),
                generator(Random.last_name, processor=to_upper_case),
                composer=composer(join, char=" ")
            )
        )

Creating a Data Entity from Other Data Entities

You might want to create a data entity from existing data entities and have the option to add more attributes as well as override behavior of existing ones.

To achieve this you can make use of the bases argument. A single base entity can be passed as a string. Multiple base entities can be passed as a list or tuple.

Single Base Data Entity

Consider the following base data entity:

# Simple base with one mandatory and one optional attr
Person = data_entity("Person", "age", fname=Random.first_name)

In the following sections, we will utilize this as base entity and make further tweaks.

Adding a Mandatory Attribute

Here the UpdatedPerson entity uses Person as its base entity and adds gender as a mandatory attribute:

# Top entity adds a mandatory attr
UpdatedPerson = data_entity("UpdatedPerson", "gender", bases=Person)
p1 = UpdatedPerson(gender="M", age=20)
p2 = UpdatedPerson(gender="M", age=20, fname="Roy")

Adding an Optional/Default Attribute

Here the UpdatedPerson entity uses Person as its base entity and adds city as an optional attribute:

# Top entity adds an optional attr
UpdatedPerson = data_entity("UpdatedPerson", city=Random.city, bases=Person)
p1 = UpdatedPerson(age=20, fname="Roy")
p2 = UpdatedPerson(age=20, fname="Roy", city="Bengaluru")

Changing Value of Optional/Default Attribute

Here the UpdatedPerson entity uses Person as its base entity and changes the value for fname attribute.

# Top entity adds an optional attr
UpdatedPerson = data_entity("UpdatedPerson", fname=Random.name, bases=Person)
p1 = UpdatedPerson(age=20)
p2 = UpdatedPerson(age=20, fname="Roy")

Converting an Optional/Default Attribute to Mandatory Attribute

Here the UpdatedPerson entity uses Person as its base entity and makes fname mandatory.

# Top entity adds an optional attr
UpdatedPerson = data_entity("UpdatedPerson", "fname", bases=Person)
p1 = UpdatedPerson(age=20, fname="Roy")

Converting a Mandatory Attribute to Optional Attribute

Here the UpdatedPerson entity uses Person as its base entity and makes age attribute optional.

# Top entity adds an optional attr
UpdatedPerson = data_entity("UpdatedPerson", age=generator(Random.fixed_length_number, length=2), bases=Person)
p1 = UpdatedPerson()

Multiple Base Data Entities

You can also assign multiple base data entities.

Simple Merged Data Entity

One simple requirement you might have is to merge two data entities together.

Here’s an intuitive approach:

Person = data_entity("Person", "age", fname=Random.first_name)
Address = data_entity("Address", city=Random.city, country=Random.country, postal_code=Random.postal_code)

# Merged Entity
PersonWithAddress = data_entity("PersonWithAddress", bases=(Person, Address))
p = PersonWithAddress(age=40)

Merged Data Entity with Custom Overrides

Sometimes the base data entities have common attributes and the top data entity also might choose to change the behaviors for more complex requirements.

Following code snippet demonstrates this:

# Simple Base 1 with one mandatory and one optional attr
Person = data_entity("Person", "age", fname=Random.first_name)

# Base 2 adds one mandatory arg, makes fname mandatory, adds one optional arg
MiddlePerson = data_entity("MiddlePerson", "gender fname", city=Random.city, bases=Person1)

# Top entity makes age optional, add one mandatory parameter
TopPerson = data_entity("TopPerson", "country", age=generator(Random.fixed_length_number, length=2), bases=(Person, MiddlePerson))
p1 = TopPerson(gender="M", fname="Roy", country="India")
p2 = TopPerson(gender="M", fname="Roy", age=15, country="India")