pytest-djangoλ μ¬λ¬ λ°μ΄ν°λ² μ΄μ€μ ν¨κ» Djangoλ₯Ό μ¬μ©ν λ ν μ€νΈ μ¬μ΄λ₯Ό μ 리νμ§ μμ΅λλ€. λ¬Έμ λ μ΄ StackOverflow μ§λ¬Έκ³Ό κ΄λ ¨μ΄ μμ΅λλ€. http://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database
db
κ³ μ μ₯μΉλ Djangoμ TestCaseλ₯Ό μ¬μ©ν©λλ€.
λ€μμ μλ db
κ³ μ μ₯μΉμ μ½λμ
λλ€.
case = TestCase(methodName='__init__')
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)
λ€μμ λ΄ μ½λμμ ν΄κ²° λ°©λ²μΌλ‘ ν¨μΉν λ°©λ²μ λλ€.
case = TestCase(methodName='__init__')
case.multi_db = True
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)
λΆλͺ ν multi_db νλκ·Έκ° μ‘΄μ¬νκ³ μ΄μ λλ¬Έμ κΈ°λ³Έκ°μ Falseμ λλ€. multi_db μ§μμ pytest_djangoμ ν΅ν©νλ μ¬λ°λ₯Έ λ°©λ²μ΄ νμ€νμ§ μμΌλ©° κ·Έλ¬ν λ³κ²½μ ν μ€νΈνλ μ¬λ°λ₯Έ λ°©λ²λ νμ€νμ§ μμ΅λλ€.
μ μ μ¬νμ΄ μμΌλ©΄ λμ΄μ€κΈ° μμ²μ μ²λ¦¬ν μ μμ΅λλ€.
λ κ°μ λ°μ΄ν°λ² μ΄μ€λ₯Ό μ¬μ©νλ ν¨μΉ λ²μ μ μ¬μ©νμ¬ μμ ν μ€νΈλ₯Ό μ 곡ν μ μμ΅λκΉ? μ¬λ¬ λ°μ΄ν°λ² μ΄μ€κ° μ¬μ©λλ λ°©μκ³Ό multi_dbλ₯Ό trueλ‘ μ€μ ν λ€μ λ€λ₯Έ λ°μ΄ν°λ² μ΄μ€μ λν μ μ΄κ° νμν μ¬νμ μ΄ν΄νλ λ° λμμ΄ λ κ²μ΄λΌκ³ μκ°ν©λλ€.
DATABASES = {
'default': {
'ENGINE' : 'django.db.backends.sqlite3',
'NAME' : 'default.db',
},
'operations': {
'ENGINE' : 'django.db.backends.sqlite3',
'NAME' : 'operations.db',
},
}
λλΆλΆμ / λͺ¨λ λͺ¨λΈμ λ λ²μ§Έ λ°μ΄ν°λ² μ΄μ€ "μμ
"κ³Ό μ°κ²°λμ΄ μμ΅λλ€. Djangoμ TestCaseλ κΈ°λ³Έ dbμμ νΈλμμ
μ μ¬λ°λ₯΄κ² νλ¬μνμ§λ§ multi_db=True
μλ ν λ€λ₯Έ dbμμλ νλ¬μνμ§ μμ κ²μ΄λΌκ³ μκ°ν©λλ€.
λ€μμ λͺ κ°μ§ μν ν μ€νΈμ λλ€.
from collection.models import Foo
def test_one(db):
Foo.objects.create()
assert Foo.objects.count() == 1
def test_two(db):
assert Foo.objects.count() == 0
λ€μμ μμμ μ€λͺ
ν ν¨μΉλ db
κ³ μ μ₯μΉμ μΆλ ₯μ
λλ€.
$ py.test multi_db_test/ -vv
======================================= test session starts =======================================
platform linux2 -- Python 2.7.2 -- py-1.4.20 -- pytest-2.5.2 -- bin/python
plugins: django
collected 2 items
multi_db_test/test_foo.py:3: test_one PASSED
multi_db_test/test_foo.py:7: test_two PASSED
==================================== 2 passed in 5.75 seconds =====================================
λ€μμ pytest_django μ체 db
κ³ μ μ₯μΉμ μΆλ ₯μ
λλ€.
$ py.test multi_db_test/ -vv
======================================= test session starts =======================================
platform linux2 -- Python 2.7.2 -- py-1.4.20 -- pytest-2.5.2 -- bin/python
plugins: django
collected 2 items
multi_db_test/test_foo.py:3: test_one PASSED
multi_db_test/test_foo.py:7: test_two FAILED
============================================ FAILURES =============================================
____________________________________________ test_two _____________________________________________
db = None
def test_two(db):
> assert Foo.objects.count() == 0
E assert 1 == 0
E + where 1 = <bound method Manager.count of <django.db.models.manager.Manager object at 0x3cd0890>>()
E + where <bound method Manager.count of <django.db.models.manager.Manager object at 0x3cd0890>> = <django.db.models.manager.Manager object at 0x3cd0890>.count
E + where <django.db.models.manager.Manager object at 0x3cd0890> = Foo.objects
multi_db_test/test_foo.py:8: AssertionError
=============================== 1 failed, 1 passed in 5.85 seconds ===============================
λ°λΌμ κ°λ¨ν 보면 multi_db
κ³ μ μ₯μΉμ pytest.mark.django_db(multi=True)
κ° APIλ‘ μλν μ μλ κ²μ²λΌ 보μ
λλ€. λ λμ μμ΄λμ΄κ° μλ μ¬λμ΄ μμ΅λκΉ?
ν©λ¦¬μ μΌλ‘ λ€λ¦½λλ€. λ μ’λ κ·Έλ μ§ μλ λλ νμ€ν λ€λ₯Έ μμ΄λμ΄κ° μμ΅λλ€.
μ¬λ³΄μΈμ. μ΄ λ¬Έμ μΈμ multi_db
μ§μμ λν λ
Όμκ° μμ΅λκΉ? λ¬Έμ μλ μ°λ½ν΄μΌ νλ€κ³ λμ μμ§λ§ νμ¬ μνλ μ΄λ»μ΅λκΉ?
@slafs μ΄ μΈμ λ€μ€ λ°μ΄ν°λ² μ΄μ€ μ§μμ λν λ Όμ/μμ²μ΄ λ§μ§ μμ΅λλ€.
multi_db=True
λ₯Ό μ λ¬νλ κ²μ λ§€μ° κ°λ¨νμ§λ§ μ΄μ λν μΌμ’
μ ν
μ€νΈλ νμνλ―λ‘ λ³΄μ‘° λ°μ΄ν°λ² μ΄μ€ μ€μ λ νμν©λλ€. λꡬλ μ§ μ΄ μμ
μ μνν μν₯μ΄ μλ€λ©΄ νλ₯ν κ²μ
λλ€. :)
μμ κ²μλ¬Όμμ @ftobiaκ° μΈκΈν ν΄κ²° λ°©λ²μ μ¬μ©νμ§λ§ 1.7μμ Django 1.8.4λ‘ μ κ·Έλ μ΄λν ν μλμ΄ μ€μ§λμμ΅λλ€.
setup/tearDownμ μ²λ¦¬νλ μ½λκ° μΈμ€ν΄μ€ μμ± λμ multi_db
ν΄λμ€ μμ±μ μ°Ύλλ‘ Djangoκ° λ³κ²½ λμμ΅λλ€.
@ftobia μ ν΄κ²° λ°©λ²μ λ€μμΌλ‘ λ³κ²½νλ©΄
case = TestCase(methodName='__init__')
case.__class__.multi_db = True
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)
+1:+1:
μ΄κ²μ Django ν μ€νΈ λ¬λμμ pytestλ‘ μ ννλ κ²μ λ§κ³ μμ΅λλ€.
κΈ°λ³Έμ μΌλ‘ https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L107μμ django_case.multi_db = True
μ¬μ©νμ§ μλ μ΄μ λ 무μμ
λκΉ?
+1
+1
django_db λ§μ»€μ λ§μ»€κ° μμΌλ©΄ μ’μ APIκ° λ κ²μ΄λΌκ³ μκ°ν©λλ€.
@pytest.mark.django_db(transactional=True, multi_db=True)
def test_a():
pass
ꡬνμ reset_sequences
(PR #308) λλ serialized_rollback
(PR #353)μ λΉκ΅μ μ μ¬ν΄μΌ ν©λλ€.
"μ΄λ €μ΄" λΆλΆμ μ¬λ¬ λ°μ΄ν°λ² μ΄μ€λ₯Ό ν¬ν¨νλλ‘ pytest-djangoμ λ΄λΆ ν μ€νΈ μ νκ΅°μ κ°μ νλ κ²μ λλ€.
λκ΅°κ°μ΄ μμ μ μνλ©΄ νμ€ν κ²ν νκ³ λ³ν©νλ λ° λμμ΄ λ κ²μ λλ€. μ΄κ²μ΄ λΉμ μκ² ν₯λ―Έλ‘μΈ κ²μ΄λΌλ©΄ PR μμ μ μμνμμμ€!
https://github.com/pytest-dev/pytest-django/pull/397#issuecomment -261987751μ λͺ κ°μ§ μ견μ λ¨κΈ°κ³ μΌλ°μ μΈ λ°©μμΌλ‘ λ³κ²½ν μ μλ PRμ λ§λ€μμ΅λλ€: https://github. com/pytest-dev/pytest-django/pull/431.com/pytest-dev/pytest-django/pull/431.com/pytest-dev/pytest-django/pull/431
μμ μ루μ μ μ°Ύλ μ¬λλ€μ μν΄ multi_dbλ₯Ό νμ±ννλ λ λ€λ₯Έ μ΅μ μ λλ€. μ΄κ²μ conftest.pyμ λ£μΌμμμ€.
def pytest_sessionstart(session):
from django.test import TransactionTestCase
TransactionTestCase.multi_db = True
ν΄κ²° λ°©λ²μ μ°Ύλ λ€λ₯Έ μ¬λμ μν΄ multi_db
λ Django 2.2μμ λ μ΄μ μ¬μ©λμ§ μμ΅λλ€. λλ₯Ό μν΄ μΌν κ΅μ²΄λ λ€μκ³Ό κ°μ΅λλ€.
TransactionTestCase.databases = set(settings.DATABASES.keys())
@jcushman μ¬μ©ν μ 체 κ³ μ μ₯μΉλ μ½λ μ‘°κ°μ 곡μ ν΄λ λ κΉμ?
μ΄μ κ° λ¬΄μμ΄λ μ°λ¦¬ κΈ°μ§ conftest.py
pytest_sessionstart()
conftest.py
μ(λ) μ무 μΌλ νμ§ μμμ΅λλ€. νμ€ pytest monkeypatch
κ³Ό ν¨κ» #342μμ νμ΄μ§λ₯Ό κ°μ Έ μμ λ€μ€ λ°μ΄ν°λ² μ΄μ€ μ¬μ©μ μν΄ λ€μμ λ°©λ¬Ένμ΅λλ€.
# OPTION 1: Function fixture; must be included with tests
@pytest.fixture
def django_db_multiple(monkeypatch, request, settings):
"""
Ensure all test functions using Django test cases have multiple database
support. This is mostly/only so that Django will wrap ALL database use with
atomic blocks like it does for DEFAULT_DB_ALIAS.
https://github.com/django/django/blob/master/django/test/testcases.py#L903
https://github.com/pytest-dev/pytest-django/issues/76
"""
from django.test import TestCase
from django.test import TransactionTestCase
db_keys = set(settings.DATABASES.keys())
monkeypatch.setattr(TestCase, 'databases', db_keys)
monkeypatch.setattr(TransactionTestCase, 'databases', db_keys)
@pytest.mark.django_db
def test_some_test(django_db_multiple):
pass
# OPTION 2: Session fixture
@pytest.fixture(autouse=True, scope='session')
def django_db_multiple():
"""
Ensure all test functions using Django test cases have multiple database
support. This is mostly/only so that Django will wrap ALL database use with
atomic blocks like it does for DEFAULT_DB_ALIAS.
https://github.com/django/django/blob/master/django/test/testcases.py#L903
https://github.com/pytest-dev/pytest-django/issues/76
https://github.com/pytest-dev/pytest/issues/1872
"""
from _pytest.monkeypatch import MonkeyPatch
from django.test import TestCase
from django.test import TransactionTestCase
from django.conf import settings
db_keys = set(settings.DATABASES.keys())
monkeypatch = MonkeyPatch()
monkeypatch.setattr(TestCase, 'databases', db_keys)
monkeypatch.setattr(TransactionTestCase, 'databases', db_keys)
yield monkeypatch
monkeypatch.undo()
_νΈμ§: μΈμ κ³ μ μ₯μΉλ‘ μ΄λνμ΅λλ€._
unittest Django ν μ€νΈ μΌμ΄μ€λ‘ μ ννμ¬ pytest μ΄λ₯λ ₯μ μμ§ μκΈ° μν΄ pytest-django λ΄λΆλ₯Ό μ΄ λͺ»μκΈ΄(μμ§ μλνλ!) ν΄νΉμ 볡μ¬νκ³ ν¨μΉνμ΅λλ€.
from typing import Optional, Type, TypeVar
import pytest
from django.test import TransactionTestCase
from pytest_django.django_compat import is_django_unittest
TestCase = TypeVar('TestCase', bound=TransactionTestCase)
def _django_db_fixture_helper(
request, django_db_blocker, transactional: bool = False, reset_sequences: bool = False,
) -> Optional[Type[TestCase]]:
if is_django_unittest(request):
return None
if not transactional and 'live_server' in request.fixturenames:
# Do nothing, we get called with transactional=True, too.
return None
django_db_blocker.unblock()
request.addfinalizer(django_db_blocker.restore)
if transactional:
from django.test import TransactionTestCase as DjangoTestCase # noqa: WPS433
if reset_sequences:
class ResetSequenceTestCase(DjangoTestCase): # noqa: WPS431
reset_sequences = True
DjangoTestCase = ResetSequenceTestCase # type: ignore[misc] # noqa: N806
else:
from django.test import TestCase as DjangoTestCase # type: ignore[no-redef] # noqa: WPS433
return DjangoTestCase # type: ignore[return-value]
@pytest.fixture()
def db_case(request, django_db_setup, django_db_blocker):
"""Require a django test database.
This database will be setup with the default fixtures and will have
the transaction management disabled. At the end of the test the outer
transaction that wraps the test itself will be rolled back to undo any
changes to the database (in case the backend supports transactions).
This is more limited than the ``transactional_db`` resource but
faster.
If multiple database fixtures are requested, they take precedence
over each other in the following order (the last one wins): ``db``,
``transactional_db``, ``django_db_reset_sequences``.
"""
if 'django_db_reset_sequences' in request.fixturenames:
request.getfixturevalue('django_db_reset_sequences')
if (
'transactional_db' in request.fixturenames
or 'live_server' in request.fixturenames
):
request.getfixturevalue('transactional_db')
else:
django_case: Optional[Type[TransactionTestCase]] = _django_db_fixture_helper(
request, django_db_blocker, transactional=False,
)
def factory(dbs=None): # noqa: WPS430
if django_case is None:
return
CaseType: Type[TransactionTestCase] = django_case # noqa: N806
if dbs is not None:
class DatabasesSetTestCase( # noqa: WPS431
CaseType, # type: ignore[valid-type, misc]
):
databases = dbs
CaseType = DatabasesSetTestCase # noqa: N806
test_case: TransactionTestCase = CaseType(methodName='__init__')
test_case._pre_setup() # type: ignore[attr-defined] # noqa: WPS437
request.addfinalizer(
test_case._post_teardown, # type: ignore[attr-defined] # noqa: WPS437
)
return factory
κ·Έκ²μΌλ‘ λ€μκ³Ό κ°μ κ²μ μ¬μ©ν μ μμ΅λλ€.
class TestCase(object):
def test_ok(
self,
db_case,
...
):
db_case(dbs=('non_default_db_alias',))
μ΄κ²μ db
κ³ μ μ₯μΉλ pytest.mark.django_db
λ§ν¬ λμ μ μ¬μ©λμ΄μΌ ν©λλ€.
@jcushman μ¬μ©ν μ 체 κ³ μ μ₯μΉλ μ½λ μ‘°κ°μ 곡μ ν΄λ λ κΉμ?
λ€λ₯Έ μ¬λμ΄ μ΄μ λΆλͺμΉ κ²½μ°λ₯Ό λλΉνμ¬ λ΅μ₯μ μΆκ°ν©λλ€.
conftest.py
μλ μ½λλ₯Ό μΆκ°ν΄μΌ ν©λλ€. μ΄ κ³ μ μ₯μΉλ μλμΌλ‘ μ νλλ―λ‘ ν
μ€νΈμ μΆκ°ν νμκ° μμ΅λλ€.
```νμ΄
def pytest_sessionstart(μΈμ
):
django.testμμ TransactionTestCase κ°μ Έμ€κΈ°
TransactionTestCase.databases = set(settings.DATABASES.keys())
````
λ°λΌμ μ΄ λ¬Έμ λ μ μ ν λ€μ€ DB μ§μμΌλ‘ κ·κ²°λλ€κ³ μκ°ν©λλ€. λλ μ΄ λ¬Έμ μ μ€λ³΅μΌλ‘ μ΄ λ¬Έμ λ₯Ό λ«μ κ²μ λλ€.
κ°μ₯ μ μ©ν λκΈ
μμ μ루μ μ μ°Ύλ μ¬λλ€μ μν΄ multi_dbλ₯Ό νμ±ννλ λ λ€λ₯Έ μ΅μ μ λλ€. μ΄κ²μ conftest.pyμ λ£μΌμμμ€.