Skip to main content

A library to generate random value for different concepts

Project description

whats the point of this library?

Graba help you to write better tests mixing concepts like "property testing" and "faker" ideas.

This library help you to make clear the concept of a test, but also it help you to explain/document some of your domain concepts.

As an example, a traditional test might look like this:

def test_user_can_delete_post():
  user = User(
    id=11,
    firstname="John",
    email="anemail@gmail.com",
    mobile="666777888"
    role="admin",
    city="NY",
    enable=True,
    department= "management"
  )
  
  post = Post(
    id=22,
    owner=user,
    content="this is the content of my post"
    showed=True
  )
  
  service_post.delete(post)
  
  assert None == service_post.find_post(id=22)

Is a simple example, but you might wonder:

  • is it relevant in the test that mobile?
  • is it relatevant if the user is enabled or the post is now showed?
  • for this test can I create a user with a different role?
  • is it good this combination of values?. For example even if the test pass, in our company an admin user works in the administration department, however this test is very loose, and is passing using department="management"

Graba reduce all this "specification noise", so you focus on the properties that really matter. So this test would look like this:

from graba.main import Any


def test_user_can_delete_post():
    any = Any()

    user = User(
        id=any.positiveInt(),
        firstname=any.word(),
        email=any.email(),
        mobile=any.mobile(),
        role=any.of(["admin", "user"]),
        city=any.word(),
        enable=True,
        department=any.of([None, "administration", "management"])
    )

    post = Post(
        id=any.positiveInt(),
        owner=user,
        content=any.sentence()
    showed = any.boolean()
    )

    service_post.delete(post)

    assert None == service_post.find_post(id=post.id)

This makes clear a few things:

-that the user must be enabled, but also that the role can be multiple, not just "admin" as we specified before.

-it doesnt matter if the post is showed or not

Now we are more clear about our tests values of interests. However still we can missconfigure the initialization of our objects. So lets give an step further


How can you leavarage the most of this library?

This library "shines" when is combined with "builder" pattern, for multiple reasons.

As an example of this:

from app.infrastructure.orm.OrmUser import OrmUser, EnumUserSessionmethod, EnumUserRole
from tests.Any import Any


class BuilderUser:

    def __init__(self):
        any = Any()
        self.id = any.positiveInt()
        self.email = any.email()
        self.created_at = any.dateTime()
        self.email = any.email()
        self.email_confirmed = any.bool()
        self.role = any.of(["admin", "user", "manager"])
        self.department = any.of([None, "management", "administration"])

    def build(self) -> User:
        return User(
            id=self.id,
            email=self.email,
            created_at=self.created_at,
            email_confirmed=self.email_confirmed,
            role=self.role,
            department=self.department
        )

    def admin(self):
        # now you can see how builder explain different types of initializing things in your applications
        # in this case, we explain that an admin is using admin role, but also is working in administration department
        self.role = "admin"
        self.department = "administration"
        return self

    def user(self):
        self.role = "user"
        self.department = None
        return self

    def manager(self):
        self.role = "manager"
        self.department = "management"
        return self

    def withEmail(self, email):
        self.email = email
        return self

    def withCreatedAt(self, created_at):
        self.created_at = created_at
        return self

    def withEmailConfirmed(self):
        self.email_confirmed = True
        return self

    def withEmailNotConfirmed(self):
        self.email_confirmed = False
        return self


class BuilderPost:

    def __init__(self):
        any = Any()
        self.id = any.email()
        self.owner = BuilderUser().build()
        self.content = any.sentene()
        self.showed = any.bool()

    def with_owner(self, owner: User):
        self.owner = user
        return self

Then in your tests you use this builder like:

from graba.main import Any

def test_user_can_delete_post():
  any = Any()
  user = BuilderUser()\
            .admin()\
            .withCreatedAt(any.dateTimeAfter(datetime(2024, 12, 16, 14, 30, 0)))\
            .build()
  
  post = BuilderPost()\
            .withOwner(user)\
            .build()
  
  service_post.delete(post)
  
  assert None == service_post.find_post(id=post.id)

But maintain builders is time consuming.....

You can be more aggressive in your development and use Graba to build any random object you have designed, gaining time. For instance:

    class Car:
        def __init__(self, color: str, engine_capacity: int, brand: str):
            self.color = color
            self.engine_capacity = engine_capacity
            self.brand = brand

        def __repr__(self):
            return f"Car(color='{self.color}', engine_capacity='{self.engine_capacity}', brand='{self.brand}')"

    class Person:
        def __init__(self,
                     colors: List[str],
                     cars: List[Car],
                     name: Optional[str] = "asdf",
                     age: int = None,
                 ):
            self.colors = colors
            self.name = name
            self.age = age
            self.cars = cars

        def __repr__(self):
            return f"Person(colors='{self.colors}', name='{self.name}', age='{self.age}', cars='{self.cars}')"

    result: Person = any.object_like(Person)

    print(result.colors) # ['bvgcshstqsqnxmlodfzzacpfmhoy', 'fcwbeu', 'qadwmuahskgreqkcbyvkyobxjkx']
    print(result.name)  # wm
    print(result.age)   # 774
    print(result.cars)  # [Car(color='pgqkoqbhnkyhdr', engine_capacity='-357', brand='bgbc'), Car(color='lwj', engine_capacity='-339', brand='itrzgouqtujuigec')]

Additional features of Graba

You can generate controlled random list of objects:

any = Any()


def createData():
    return {
        "age": any.positiveInt(),
        "name": any.word()
    }


result = any.listOf(
    min=3,
    max=7,
    factoryFunction=createData
)
print(result)
# [
#     {'age': 7323, 'name': 'vecalmzbdcvdwuqk'},
#     {'age': 9705, 'name': 'bdqqpgtpgbfbci'},
#     {'age': 9656, 'name': 'ojizqxl'}
# ]

You can "pick" random items from a list:

result = any().subsetOf(min=1, max=4, items=["a", "b", "c", "d", "e", "f"])
print(result) # ['d', 'e', 'b']

You can generate random dates upon conditions:

result = any().dateTimeBefore("2022-10-10 23:11:05")
print(result) # 2016-12-28 23:11:05


result = any().datetimeBetween("2023-10-10", "2027-09-09")
print(result) # 2025-08-10 22:00:19

Being realistic: working with "data is dirty" mode

Let's be honest. In real life you cannot expect to recieve all the data "clean". Sometimes you receive a number or a boolean as string, others your strings are not trimmed, others your null are not consisten so you might receive "null", None, "none", etc.

Graba consider that testing all these edge cases and observe how your application performs is of interests. As an example of working with fuzzy mode you can do:

from graba.main import Any


def test_user_can_delete_post():
    any = Any(mode_datadirty=True)  # <--- IMPORTANT LINE

    user = User(
        id=any.positiveInt(),  # This might be one of: 23, "23"
        firstname=any.word(),  # This might be one of: " MYword", "MYword", "MYword ", ...
        email=any.word(),
        mobile=any.mobile(),
        role=any.of(["admin", "user"]),
        city=any.word(),
        enable=True,
        department=any.of([None, "administration", "management"])
    )

    post = Post(
        id=any.positiveInt(),  # This might be one of: 34, "34"
        owner=user,
        content=any.sentence(),
        showed=any.boolean()  # This might be one of: "true", True, "True"
    )

    service_post.delete(post)

    service_post.find_post(id=post.id)  # exception!!!!, post.id is not an integer

In this case, fuzzy_mode will teach that might be interesting to check some values in the id and possibly make a proper test. This is so because python is not typed, but with this you can put an extra layer for peace of mind :)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

graba-0.3.24.tar.gz (7.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

graba-0.3.24-py3-none-any.whl (6.7 kB view details)

Uploaded Python 3

File details

Details for the file graba-0.3.24.tar.gz.

File metadata

  • Download URL: graba-0.3.24.tar.gz
  • Upload date:
  • Size: 7.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.1

File hashes

Hashes for graba-0.3.24.tar.gz
Algorithm Hash digest
SHA256 d8817e813686297cad3da05844af1ba9621246dd6361efbab3cfd15c45b10175
MD5 167dbb2044752bda1310ee5f3315d2f2
BLAKE2b-256 876fc554f92664be78999023626a4aa310a54f15f3bfc60eccb82ade7a4b672f

See more details on using hashes here.

File details

Details for the file graba-0.3.24-py3-none-any.whl.

File metadata

  • Download URL: graba-0.3.24-py3-none-any.whl
  • Upload date:
  • Size: 6.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.1

File hashes

Hashes for graba-0.3.24-py3-none-any.whl
Algorithm Hash digest
SHA256 8dd6e42a83144f92c5df5aabdca23c434a70164a8042d8b258935c793e3673e3
MD5 0a6377058dedf62484ee047b166fd3a9
BLAKE2b-256 21362c7aa6eaac54ee276f732ab0ac97a69c9a3c161bc9986645cc8318f835e1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page