# 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:
```python
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:
```python
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:
```python
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:
```python
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)
```
# Additional features of Graba
You can generate controlled random list of objects:
```python
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:
```python
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:
```python
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:
```python
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 :)
Raw data
{
"_id": null,
"home_page": "https://github.com/tatitati/graba",
"name": "graba",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": null,
"keywords": null,
"author": "Francisco Jose Albert Albusac",
"author_email": "francis.jaa@protonmail.com",
"download_url": "https://files.pythonhosted.org/packages/8d/83/1588c131eebe8ae5d98059f3ca38998076277588eb127b903f64e3024225/graba-0.3.23.tar.gz",
"platform": null,
"description": "# whats the point of this library?\n\nGraba help you to write better tests mixing concepts like \"property testing\" and \"faker\" ideas.\n\nThis library help you to make clear the concept of a test, but also it help you to explain/document some of your domain concepts.\n\nAs an example, a traditional test might look like this:\n\n\n\n```python\ndef test_user_can_delete_post():\n user = User(\n id=11,\n firstname=\"John\",\n email=\"anemail@gmail.com\",\n mobile=\"666777888\"\n role=\"admin\",\n city=\"NY\",\n enable=True,\n department= \"management\"\n )\n \n post = Post(\n id=22,\n owner=user,\n content=\"this is the content of my post\"\n showed=True\n )\n \n service_post.delete(post)\n \n assert None == service_post.find_post(id=22)\n```\n\nIs a simple example, but you might wonder: \n\n - is it relevant in the test that mobile?\n - is it relatevant if the user is enabled or the post is now showed?\n - for this test can I create a user with a different role?\n - 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\"\n\n\n\nGraba reduce all this \"specification noise\", so you focus on the properties that really matter. So this test would look like this:\n\n```python\nfrom graba.main import Any\n\n\ndef test_user_can_delete_post():\n any = Any()\n\n user = User(\n id=any.positiveInt(),\n firstname=any.word(),\n email=any.email(),\n mobile=any.mobile(),\n role=any.of([\"admin\", \"user\"]),\n city=any.word(),\n enable=True,\n department=any.of([None, \"administration\", \"management\"])\n )\n\n post = Post(\n id=any.positiveInt(),\n owner=user,\n content=any.sentence()\n showed = any.boolean()\n )\n\n service_post.delete(post)\n\n assert None == service_post.find_post(id=post.id)\n```\n\nThis makes clear a few things:\n\n-that the user must be enabled, but also that the role can be multiple, not just \"admin\" as we specified before.\n\n-it doesnt matter if the post is showed or not\n\n\n\nNow 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\n\n----\n\n# How can you leavarage the most of this library?\n\nThis library \"shines\" when is combined with \"builder\" pattern, for multiple reasons.\n\nAs an example of this:\n\n```python\nfrom app.infrastructure.orm.OrmUser import OrmUser, EnumUserSessionmethod, EnumUserRole\nfrom tests.Any import Any\n\n\nclass BuilderUser:\n\n def __init__(self):\n any = Any()\n self.id = any.positiveInt()\n self.email = any.email()\n self.created_at = any.dateTime()\n self.email = any.email()\n self.email_confirmed = any.bool()\n self.role = any.of([\"admin\", \"user\", \"manager\"])\n self.department = any.of([None, \"management\", \"administration\"])\n\n def build(self) -> User:\n return User(\n id=self.id,\n email=self.email,\n created_at=self.created_at,\n email_confirmed=self.email_confirmed,\n role=self.role,\n department=self.department\n )\n\n def admin(self):\n # now you can see how builder explain different types of initializing things in your applications\n # in this case, we explain that an admin is using admin role, but also is working in administration department\n self.role = \"admin\"\n self.department = \"administration\"\n return self\n\n def user(self):\n self.role = \"user\"\n self.department = None\n return self\n\n def manager(self):\n self.role = \"manager\"\n self.department = \"management\"\n return self\n\n def withEmail(self, email):\n self.email = email\n return self\n\n def withCreatedAt(self, created_at):\n self.created_at = created_at\n return self\n\n def withEmailConfirmed(self):\n self.email_confirmed = True\n return self\n\n def withEmailNotConfirmed(self):\n self.email_confirmed = False\n return self\n\n\nclass BuilderPost:\n\n def __init__(self):\n any = Any()\n self.id = any.email()\n self.owner = BuilderUser().build()\n self.content = any.sentene()\n self.showed = any.bool()\n\n def with_owner(self, owner: User):\n self.owner = user\n return self\n```\n\nThen in your tests you use this builder like:\n\n```python\nfrom graba.main import Any\n\ndef test_user_can_delete_post():\n any = Any()\n user = BuilderUser()\\\n .admin()\\\n .withCreatedAt(any.dateTimeAfter(datetime(2024, 12, 16, 14, 30, 0)))\\\n .build()\n \n post = BuilderPost()\\\n .withOwner(user)\\\n .build()\n \n service_post.delete(post)\n \n assert None == service_post.find_post(id=post.id)\n```\n\n# Additional features of Graba\n\nYou can generate controlled random list of objects:\n\n```python\nany = Any()\n\n\ndef createData():\n return {\n \"age\": any.positiveInt(),\n \"name\": any.word()\n }\n\n\nresult = any.listOf(\n min=3,\n max=7,\n factoryFunction=createData\n)\nprint(result)\n# [\n# {'age': 7323, 'name': 'vecalmzbdcvdwuqk'},\n# {'age': 9705, 'name': 'bdqqpgtpgbfbci'},\n# {'age': 9656, 'name': 'ojizqxl'}\n# ]\n```\n\nYou can \"pick\" random items from a list:\n\n```python\nresult = any().subsetOf(min=1, max=4, items=[\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"])\nprint(result) # ['d', 'e', 'b']\n```\n\nYou can generate random dates upon conditions:\n\n```python\nresult = any().dateTimeBefore(\"2022-10-10 23:11:05\")\nprint(result) # 2016-12-28 23:11:05\n\n\nresult = any().datetimeBetween(\"2023-10-10\", \"2027-09-09\")\nprint(result) # 2025-08-10 22:00:19\n```\n\n# Being realistic: working with \"data is dirty\" mode\n\nLet'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\nstrings are not trimmed, others your null are not consisten so you might receive \"null\", None, \"none\", etc.\n\nGraba consider that testing all these edge cases and observe how your application performs is of interests.\nAs an example of working with fuzzy mode you can do:\n\n```python\nfrom graba.main import Any\n\n\ndef test_user_can_delete_post():\n any = Any(mode_datadirty=True) # <--- IMPORTANT LINE\n\n user = User(\n id=any.positiveInt(), # This might be one of: 23, \"23\"\n firstname=any.word(), # This might be one of: \" MYword\", \"MYword\", \"MYword \", ...\n email=any.word(),\n mobile=any.mobile(),\n role=any.of([\"admin\", \"user\"]),\n city=any.word(),\n enable=True,\n department=any.of([None, \"administration\", \"management\"])\n )\n\n post = Post(\n id=any.positiveInt(), # This might be one of: 34, \"34\"\n owner=user,\n content=any.sentence(),\n showed=any.boolean() # This might be one of: \"true\", True, \"True\"\n )\n\n service_post.delete(post)\n\n service_post.find_post(id=post.id) # exception!!!!, post.id is not an integer\n```\n\nIn 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\nso because python is not typed, but with this you can put an extra layer for peace of mind :)\n",
"bugtrack_url": null,
"license": null,
"summary": "A library to generate random value for different concepts",
"version": "0.3.23",
"project_urls": {
"Homepage": "https://github.com/tatitati/graba"
},
"split_keywords": [],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "8f1a3b246be8e69311a3a1f7ab821edab7ac712f3dcabe91055abe0fb04e8f3c",
"md5": "d87c6c8c96444790e53b7bf923738387",
"sha256": "78df356db5cba2b686ad08b1b994cedb987b1c4408f92e9cb9c8ddf2955d06c8"
},
"downloads": -1,
"filename": "graba-0.3.23-py3-none-any.whl",
"has_sig": false,
"md5_digest": "d87c6c8c96444790e53b7bf923738387",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 6217,
"upload_time": "2025-01-24T00:32:45",
"upload_time_iso_8601": "2025-01-24T00:32:45.654575Z",
"url": "https://files.pythonhosted.org/packages/8f/1a/3b246be8e69311a3a1f7ab821edab7ac712f3dcabe91055abe0fb04e8f3c/graba-0.3.23-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "8d831588c131eebe8ae5d98059f3ca38998076277588eb127b903f64e3024225",
"md5": "5874b7101f1007c1b7b184660247cde4",
"sha256": "d613995533f1b0864077a5aadd39b8636d64d742086fd1e6eb16b68de37085d9"
},
"downloads": -1,
"filename": "graba-0.3.23.tar.gz",
"has_sig": false,
"md5_digest": "5874b7101f1007c1b7b184660247cde4",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 7577,
"upload_time": "2025-01-24T00:32:52",
"upload_time_iso_8601": "2025-01-24T00:32:52.897253Z",
"url": "https://files.pythonhosted.org/packages/8d/83/1588c131eebe8ae5d98059f3ca38998076277588eb127b903f64e3024225/graba-0.3.23.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-01-24 00:32:52",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "tatitati",
"github_project": "graba",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "graba"
}