added better test fixtures

This commit is contained in:
Alexander Karpov 2023-09-08 16:17:35 +03:00
parent 1fc003636b
commit b59c8fbf8b
8 changed files with 333 additions and 54 deletions

View File

@ -1,14 +1,122 @@
import pytest
import io
import logging
import re
from akarpov.users.models import User
from akarpov.users.tests.factories import UserFactory
import factory
import pytest
from django.core.files.uploadedfile import SimpleUploadedFile
from pytest_django.lazy_django import skip_if_no_django
from rest_framework.test import APIClient
from akarpov.utils.config import build_redis_uri
from akarpov.utils.faker import configure_factory_faker, configure_faker
from akarpov.utils.pytest_factoryboy import autodiscover_factories
configure_factory_faker(factory.Faker)
autodiscover_factories()
logger = logging.getLogger(__name__)
@pytest.fixture(autouse=True)
def media_storage(settings, tmpdir):
settings.MEDIA_ROOT = tmpdir.strpath
def activate_django_db(db):
pass
@pytest.fixture(scope="session", autouse=True)
def update_config():
from django.conf import settings
settings.DEBUG = True
@pytest.fixture
def user(db) -> User:
return UserFactory()
def api_client():
return APIClient()
@pytest.fixture
def admin_client(client, user_factory):
admin_user = user_factory(is_superuser=True, is_staff=True)
client.force_login(admin_user)
return client
@pytest.fixture
def api_user_client(api_client, user):
api_client.force_authenticate(user)
return api_client
@pytest.fixture
def image_factory():
def factory(filename="test.jpg", **params):
from PIL import Image
width = params.get("width", 520)
height = params.get("height", width)
color = params.get("color", "blue")
image_format = params.get("format", "JPEG")
image_palette = params.get("palette", "RGB")
thumb_io = io.BytesIO()
with Image.new(image_palette, (width, height), color) as thumb:
thumb.save(thumb_io, format=image_format)
return SimpleUploadedFile(filename, thumb_io.getvalue())
return factory
@pytest.fixture
def image(image_factory):
return image_factory()
@pytest.fixture
def uploaded_photo(image):
return SimpleUploadedFile("test.png", image.read())
@pytest.fixture
def plain_file():
return io.BytesIO(b"plain_text")
@pytest.fixture(scope="session", autouse=True)
def faker_session_locale():
return ["en"]
@pytest.fixture(autouse=True)
def add_faker_providers(faker):
configure_faker(faker)
return faker
@pytest.fixture(scope="session", autouse=True)
def set_test_redis_databases(request):
from django.conf import settings
skip_if_no_django()
xdist_worker = getattr(request.config, "workerinput", {}).get("workerid")
if xdist_worker is None:
return
worker_number_search = re.search(r"\d+", xdist_worker)
if not worker_number_search:
return
max_db_number = worker_number_search[0]
max_db_number = int(max_db_number) * 3 + 2
channels_redis_url = build_redis_uri(
settings.CHANNELS_REDIS_HOST,
settings.CHANNELS_REDIS_PORT,
settings.CHANNELS_REDIS_USER,
settings.CHANNELS_REDIS_PASSWORD,
max_db_number,
)
settings.CHANNEL_LAYERS["default"]["CONFIG"]["hosts"][0][
"address"
] = channels_redis_url
settings.CHANNELS_REDIS_DB = max_db_number
settings.CLICKHOUSE_REDIS_CONFIG["db"] = max_db_number - 1
settings.CELERY_REDIS_DB = max_db_number - 2

View File

@ -1,45 +1,20 @@
from collections.abc import Sequence
from typing import Any
from django.contrib.auth import get_user_model
from factory import Faker, post_generation
import factory.fuzzy
from factory.django import DjangoModelFactory
from akarpov.utils.faker import django_image
from akarpov.utils.pytest_factoryboy import global_register
@global_register
class UserFactory(DjangoModelFactory):
username = Faker("user_name")
email = Faker("email")
name = Faker("name")
about = Faker("text")
@post_generation
def password(self, create: bool, extracted: Sequence[Any], **kwargs):
password = (
extracted
if extracted
else Faker(
"password",
length=42,
special_chars=True,
digits=True,
upper_case=True,
lower_case=True,
).evaluate(None, None, extra={"locale": None})
)
self.set_password(password)
@post_generation
def image(self, create, extracted, **kwargs):
if extracted:
image_name, image = extracted
else:
image_name = "test.jpg"
image = django_image(image_name, **kwargs)
self.image.save(image_name, image)
email = factory.Sequence(lambda i: f"user_{i}@akarpov.ru")
username = factory.Faker("word")
image = factory.fuzzy.FuzzyText(prefix="https://img")
password = "P@ssw0rd"
class Meta:
model = get_user_model()
skip_postgeneration_save = False
django_get_or_create = ["username"]
model = "users.User"
@classmethod
def _create(cls, model_class, *args, **kwargs):
manager = cls._get_manager(model_class)
return manager.create_user(*args, **kwargs)

View File

@ -1,17 +1,19 @@
from akarpov.files.consts import USER_INITIAL_FILE_UPLOAD
from akarpov.users.models import User
def test_user_create(user: User):
def test_user_create(user_factory):
user = user_factory()
password = "123"
user.set_password(password)
assert user.check_password(password)
def test_auto_file_upload_size(user: User):
def test_auto_file_upload_size(user_factory):
user = user_factory()
size = USER_INITIAL_FILE_UPLOAD
assert user.left_file_upload == size
def test_user_image_create(user: User):
def test_user_image_create(user_factory):
user = user_factory()
assert user.image

View File

@ -15,9 +15,9 @@ def money(self):
def configure_factory_faker(factory_faker):
factory_faker._DEFAULT_LOCALE = "ru_RU"
factory_faker._DEFAULT_LOCALE = "en"
for provider in additional_providers:
factory_faker.add_provider(provider, locale="ru_RU")
factory_faker.add_provider(provider, locale="en")
def configure_faker(faker):

View File

@ -0,0 +1,47 @@
from django.utils.module_loading import autodiscover_modules
from pytest_factoryboy.fixture import get_caller_locals, register
class RegisteredFactory:
def __init__(self, factory_class, args, kwargs):
self.factory_class = factory_class
self.args = args
self.kwargs = kwargs
factory_registry = set() # set of registered factories
def global_register(factory_class=None, *args, **kwargs):
if factory_class is None:
def _global_register(factory_class):
return global_register(factory_class, *args, **kwargs)
return _global_register
factory_registry.add(RegisteredFactory(factory_class, args, kwargs))
return factory_class
def autodiscover_factories():
assert (
not factory_registry
), "You've already called `autodiscover_factories` function"
caller_locals = get_caller_locals()
assert caller_locals["__name__"].endswith(
"conftest"
), "You must call `autodiscover_factories` from `conftest.py` file"
autodiscover_modules("tests.factories")
for registered_factory in factory_registry:
register(
registered_factory.factory_class,
*registered_factory.args,
_caller_locals=caller_locals,
**registered_factory.kwargs,
)

143
poetry.lock generated
View File

@ -213,6 +213,18 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (>=0.22)"]
[[package]]
name = "appdirs"
version = "1.4.4"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "main"
optional = false
python-versions = "*"
files = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
[[package]]
name = "appnope"
version = "0.1.3"
@ -2213,6 +2225,21 @@ files = [
dnspython = ">=2.0.0"
idna = ">=2.0.0"
[[package]]
name = "execnet"
version = "2.0.2"
description = "execnet: rapid multi-Python deployment"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"},
{file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"},
]
[package.extras]
testing = ["hatch", "pre-commit", "pytest", "tox"]
[[package]]
name = "executing"
version = "1.2.0"
@ -3589,6 +3616,26 @@ html5 = ["html5lib"]
htmlsoup = ["BeautifulSoup4"]
source = ["Cython (>=0.29.35)"]
[[package]]
name = "mako"
version = "1.2.4"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"},
{file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"},
]
[package.dependencies]
MarkupSafe = ">=0.9.2"
[package.extras]
babel = ["Babel"]
lingua = ["lingua"]
testing = ["pytest"]
[[package]]
name = "markdown"
version = "3.4.4"
@ -5202,6 +5249,25 @@ pluggy = ">=0.12,<2.0"
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-asyncio"
version = "0.21.1"
description = "Pytest support for asyncio"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"},
{file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"},
]
[package.dependencies]
pytest = ">=7.0.0"
[package.extras]
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
[[package]]
name = "pytest-django"
version = "4.5.2"
@ -5221,6 +5287,60 @@ pytest = ">=5.4.0"
docs = ["sphinx", "sphinx-rtd-theme"]
testing = ["Django", "django-configurations (>=2.0)"]
[[package]]
name = "pytest-factoryboy"
version = "2.3.1"
description = "Factory Boy support for pytest."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-factoryboy-2.3.1.tar.gz", hash = "sha256:5102ce8597d1d8db2436f1fcfe96635f212801e18e14c2f04327a9343b9b56d7"},
{file = "pytest_factoryboy-2.3.1-py3-none-any.whl", hash = "sha256:e8c249c8c5c195ecd46f86377dbe92451372295a9485d42060e3f82ee23b1ff6"},
]
[package.dependencies]
appdirs = "*"
factory-boy = ">=2.10.0"
inflection = "*"
mako = "*"
pytest = ">=5.0.0"
typing-extensions = "*"
[[package]]
name = "pytest-lambda"
version = "2.2.0"
description = "Define pytest fixtures with lambda functions."
category = "main"
optional = false
python-versions = ">=3.7.0,<4.0.0"
files = [
{file = "pytest-lambda-2.2.0.tar.gz", hash = "sha256:f8af7b011980b04499d906161b69e7df984a8b16285a17ca97eb133a0775dd58"},
{file = "pytest_lambda-2.2.0-py3-none-any.whl", hash = "sha256:ad77ff48a514379bfccb404ba9d3f5871c2a0817b47ccc8abbad430d6d450ef4"},
]
[package.dependencies]
pytest = ">=3.6,<8"
wrapt = ">=1.11.0,<2.0.0"
[[package]]
name = "pytest-mock"
version = "3.11.1"
description = "Thin-wrapper around the mock package for easier use with pytest"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"},
{file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"},
]
[package.dependencies]
pytest = ">=5.0"
[package.extras]
dev = ["pre-commit", "pytest-asyncio", "tox"]
[[package]]
name = "pytest-sugar"
version = "0.9.7"
@ -5241,6 +5361,27 @@ termcolor = ">=2.1.0"
[package.extras]
dev = ["black", "flake8", "pre-commit"]
[[package]]
name = "pytest-xdist"
version = "3.3.1"
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"},
{file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"},
]
[package.dependencies]
execnet = ">=1.1"
pytest = ">=6.2.0"
[package.extras]
psutil = ["psutil (>=3.0)"]
setproctitle = ["setproctitle"]
testing = ["filelock"]
[[package]]
name = "python-crontab"
version = "3.0.0"
@ -8043,4 +8184,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "78a5ef3c902cae4b5e808da8eed517b8b649800ca567d67a7c004eb1b5de8147"
content-hash = "07c6933d3ce4f4d081cffbe7b7ad4e3561a0353e1f8157961916ca7502549914"

View File

@ -102,6 +102,11 @@ requests = ">=2.25"
spacy = {extras = ["lookups"], version = "^3.6.1"}
spacy-transformers = "^1.2.5"
extract-msg = "0.28.7"
pytest-factoryboy = "2.3.1"
pytest-xdist = "^3.3.1"
pytest-mock = "^3.11.1"
pytest-asyncio = "^0.21.1"
pytest-lambda = "^2.2.0"
[build-system]

View File

@ -1,3 +1,4 @@
[pytest]
addopts = --ds=config.settings.test --reuse-db
python_files = tests.py test_*.py
DJANGO_SETTINGS_MODULE = config.settings.test
python_files = tests.py test_*.py *_tests.py
addopts = --reuse-db