Pytest-django: 'mail.outbox'κ°€ μ„€μ •λ˜μ§€ μ•ŠμŠ΅λ‹ˆκΉŒ?

에 λ§Œλ“  2018λ…„ 04μ›” 10일  Β·  14μ½”λ©˜νŠΈ  Β·  좜처: pytest-dev/pytest-django

μ•ˆλ…•ν•˜μ„Έμš”, pytest-django κ°μ‚¬ν•©λ‹ˆλ‹€! λͺ¨λ“  λ…Έλ ₯에 κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€. 문제 없이 κ½€ μ—¬λŸ¬ 번 μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€.

일뢀 ν•­λͺ©μ„ μ—…κ·Έλ ˆμ΄λ“œν–ˆλŠ”λ° κ°‘μžκΈ° mail.outbox μ—μ„œ ν…ŒμŠ€νŠΈ 쀑에 이메일을 λ³΄κ΄€ν•˜μ§€ μ•ŠλŠ” λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‚΄κ°€ μ‚¬μš©ν•˜λŠ” 방법을 μ •ν™•νžˆ μ•Œμ•„ λ‚΄κΈ° μœ„ν•΄ λ…Έλ ₯ν–ˆμŠ΅λ‹ˆλ‹€ mailoutbox μ„€λͺ… λœλŒ€λ‘œ 고정을 μ—¬κΈ° ν•˜μ§€λ§Œμ΄λ₯Ό μ‚¬μš©ν•˜λŠ” 데 λ¬Έμ œκ°€ μžˆμ–΄μš”.

μ§€κΈˆ μš°λ¦¬λŠ” ν΄λž˜μŠ€μ—μ„œ ν…ŒμŠ€νŠΈ 쀑이며 mailoutbox κ³ μ • μž₯치λ₯Ό μ „λ‹¬ν•˜κΈ° μœ„ν•΄ κ½€ λ§Žμ€ ꡬ성을 μ‹œλ„ν–ˆμŠ΅λ‹ˆλ‹€. 즉, λ‚˜λŠ” iniλ₯Ό μ—…λ°μ΄νŠΈν–ˆμŠ΅λ‹ˆλ‹€.

[pytest]
usefixtures = mailoutbox

κ·Έ κ³ μ • μž₯치λ₯Ό μ‚¬μš©ν•˜λŠ” 방법을 μ •ν™•νžˆ μ•Œμ§€ λͺ»ν•˜μ§€λ§Œ 클래슀 λ©”μ„œλ“œμ— μœ„μΉ˜ 인수 mailoutbox λ₯Ό μΆ”κ°€ν•˜λ €κ³  μ‹œλ„ν–ˆμ§€λ§Œ μ—¬μ „νžˆ μ•‘μ„ΈμŠ€ν•  수 μ—†μŠ΅λ‹ˆλ‹€. κ°„λ‹¨ν•œ 것이 λˆ„λ½λœ 것 κ°™μŠ΅λ‹ˆλ‹€.

μ˜¬λ°”λ₯Έ λ°©ν–₯으둜 좔진해 μ£Όμ‹œλ©΄ κ°μ‚¬ν•˜κ² μŠ΅λ‹ˆλ‹€. 이 ν”„λ‘œμ νŠΈμ— λ‹€μ‹œ ν•œ 번 κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€!

κ°€μž₯ μœ μš©ν•œ λŒ“κΈ€

λ‚˜λŠ” 같은 λ¬Έμ œκ°€ μžˆλ‹€κ³  μƒκ°ν•˜λ―€λ‘œ ν…ŒμŠ€νŠΈλ₯Ό κΈ°λ³Έ 이메일 μ „μ†‘μœΌλ‘œ 쀄이고 κ°œλ³„μ μœΌλ‘œ μ‹€ν–‰ν•˜κΈ°λ‘œ κ²°μ •ν–ˆμŠ΅λ‹ˆλ‹€.

def test_reset_password(mailoutbox, db, settings):
    text_message = render_to_string('emails/password_reset/password_reset_successful.txt',
                                    context={})
    html_message = render_to_string('emails/password_reset/password_reset_successful.html',
                                    context={})
    subject = render_to_string('emails/password_reset/password_reset_successful_subject.txt',
                               context={})

    email = EmailMultiAlternatives(
        subject=subject,
        body=text_message,
        from_email="[email protected]",
        to=['[email protected]']
    )

    if html_message:
        email.attach_alternative(html_message, "text/html")
    email.send()

    print(settings.EMAIL_BACKEND)

    assert len(mailoutbox) == 1
>       assert len(mailoutbox) == 1
E       assert 0 == 1
E        +  where 0 = len([])

accounts/tests/api/test_reset_password.py:200: AssertionError
----------------------------------------------- Captured stdout call ------------------------------------------------
django.core.mail.backends.locmem.EmailBackend
--------------------------------------------- Captured stdout teardown ----------------------------------------------

이제 멋진 뢀뢄은 db κ³ μ • μž₯치λ₯Ό μ œκ±°ν•˜λ©΄ ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν•œλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. κ·Έλž˜μ„œ django_db_setup λ©”μ†Œλ“œλ₯Ό μ‚­μ œν•˜λ €κ³  μ‹œλ„ν–ˆμ§€λ§Œ 아무 μ†Œμš©μ΄ μ—†μ—ˆμŠ΅λ‹ˆλ‹€ ...

μ§€κΈˆ λ§‰ν˜”μ–΄μš”.

λͺ¨λ“  14 λŒ“κΈ€

https://github.com/pytest-dev/pytest-django/blob/5da0935731d71aa347c57cd1753f51e3ba9f32d5/docs/helpers.rst#clearing -of-mailoutbox(7aee367)λ₯Ό μ°Έμ‘°
(문제λ₯Ό μ œλŒ€λ‘œ μ΄ν•΄ν•˜κ³  μžˆλŠ”μ§€ ν™•μ‹€ν•˜μ§€ μ•ŠμŒ)

λ‚˜λŠ” 같은 λ¬Έμ œκ°€ μžˆλ‹€κ³  μƒκ°ν•˜λ―€λ‘œ ν…ŒμŠ€νŠΈλ₯Ό κΈ°λ³Έ 이메일 μ „μ†‘μœΌλ‘œ 쀄이고 κ°œλ³„μ μœΌλ‘œ μ‹€ν–‰ν•˜κΈ°λ‘œ κ²°μ •ν–ˆμŠ΅λ‹ˆλ‹€.

def test_reset_password(mailoutbox, db, settings):
    text_message = render_to_string('emails/password_reset/password_reset_successful.txt',
                                    context={})
    html_message = render_to_string('emails/password_reset/password_reset_successful.html',
                                    context={})
    subject = render_to_string('emails/password_reset/password_reset_successful_subject.txt',
                               context={})

    email = EmailMultiAlternatives(
        subject=subject,
        body=text_message,
        from_email="[email protected]",
        to=['[email protected]']
    )

    if html_message:
        email.attach_alternative(html_message, "text/html")
    email.send()

    print(settings.EMAIL_BACKEND)

    assert len(mailoutbox) == 1
>       assert len(mailoutbox) == 1
E       assert 0 == 1
E        +  where 0 = len([])

accounts/tests/api/test_reset_password.py:200: AssertionError
----------------------------------------------- Captured stdout call ------------------------------------------------
django.core.mail.backends.locmem.EmailBackend
--------------------------------------------- Captured stdout teardown ----------------------------------------------

이제 멋진 뢀뢄은 db κ³ μ • μž₯치λ₯Ό μ œκ±°ν•˜λ©΄ ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν•œλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. κ·Έλž˜μ„œ django_db_setup λ©”μ†Œλ“œλ₯Ό μ‚­μ œν•˜λ €κ³  μ‹œλ„ν–ˆμ§€λ§Œ 아무 μ†Œμš©μ΄ μ—†μ—ˆμŠ΅λ‹ˆλ‹€ ...

μ§€κΈˆ λ§‰ν˜”μ–΄μš”.

mailoutbox κ³ μ • μž₯μΉ˜κ°€ μž‘λ™ν•˜λŠ” κ²ƒμ²˜λŸΌ λ³΄μ΄λŠ” λ™μΌν•œ ν”„λ‘œμ νŠΈμ— λͺ‡ 가지 ν…ŒμŠ€νŠΈκ°€ μžˆλ‹€λŠ” 것을 μžŠμ–΄λ²„λ ΈκΈ° λ•Œλ¬Έμ— ν•΄λ‹Ή ν…ŒμŠ€νŠΈμ™€ μ‹€νŒ¨ν•œ ν…ŒμŠ€νŠΈ μ‚¬μ΄μ˜ 차이점을 찾아보렀고 ν–ˆμŠ΅λ‹ˆλ‹€.
λ‚΄κ°€ 찾은 μœ μΌν•œ 차이점은 ν…ŒμŠ€νŠΈ ν•¨μˆ˜μ—μ„œ mailoutbox 인수의 μœ„μΉ˜μž…λ‹ˆλ‹€. db κ³ μ • μž₯μΉ˜μ— μ˜μ‘΄ν•˜λŠ” κ³ μ • μž₯치 μ•žμ— 있으면 μž‘λ™ν•˜μ§€ μ•Šμ§€λ§Œ 뒀에 λ„£μœΌλ©΄ μž‘λ™ν•˜λ―€λ‘œ ν•΄κ²° 방법을 찾은 것 κ°™μŠ΅λ‹ˆλ‹€.

pytest-django==3.3.3μ—μ„œλ„ 이 λ™μž‘μ„ 보고 μžˆμŒμ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

@bogdanpetrea
쑰사/μ—…λ°μ΄νŠΈμ— κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€.
λˆ„κ΅°κ°€ 디버깅/μˆ˜μ •ν•˜λŠ” 데 도움이 될 κ²ƒμž…λ‹ˆλ‹€.

μ‹€μ œλ‘œ 관련이 μžˆλŠ”μ§€ ν™•μ‹€ν•˜μ§€ μ•Šμ§€λ§Œ mail.outbox κ°€ μ²˜μŒμ— μ„€μ •λ˜μ§€ μ•Šμ•„ AttributeErrorκ°€ μžˆμ„ 수 μžˆμŒμ„ λ°œκ²¬ν–ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ 이것은 pytest-djangoκ°€ νšŒμ „ν•˜λŠ” λ™μ•ˆ ν™˜κ²½μ„ λ³€κ²½ν•˜λŠ” 것과 관련이 μžˆμŠ΅λ‹ˆλ‹€.
μ°Έμ‘°: https://github.com/pytest-dev/pytest-django/pull/708

λ‚˜λŠ” ν˜„μž¬ λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€. κ·Έ μš°νŽΈν•¨μ€ λΉ„μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€ :(
μ΅œμ‹  ν…ŒμŠ€νŠΈ ν•¨μˆ˜ μΈμˆ˜μž…λ‹ˆλ‹€.

νŽΈμ§‘: μ‹€μ œ ν…ŒμŠ€νŠΈ μ½”λ“œ 전에 mailoutbox.clear() λ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€.

@μ œλ””
μž¬ν˜„ κ°€λŠ₯ν•œ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μ œκ³΅ν•  수 μžˆμŠ΅λ‹ˆκΉŒ? λ¬Όλ‘  pytest-django에 λŒ€ν•œ μ‹€νŒ¨ν•œ ν…ŒμŠ€νŠΈκ°€ κ°€μž₯ μ’‹μŠ΅λ‹ˆλ‹€.

#708 κ΄€λ ¨? (즉, 거기에 λ‹ΏλŠ” μ½”λ“œ μ£Όμœ„λ₯Ό 찌λ₯΄μ‹­μ‹œμ˜€)

문제λ₯Ό μž¬ν˜„ν•˜λŠ” κ°„λ‹¨ν•œ ν…ŒμŠ€νŠΈ 사둀λ₯Ό λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€(적어도 λ‚΄κ°€ κ²ͺκ³  μžˆλŠ” 방식): https://github.com/pytest-dev/pytest-django/compare/master...koniiiik :589- mailoutbox-is-not-django-core-mail-outbox?expand=1

mailoutbox μ‚¬μš©ν•œ λ°©λ²•μ—μ„œ mailoutbox.clear() μ‹€μ œλ‘œ mailoutbox 및 mail.outbox λͺ¨λ‘ μ§€μšΈ 수 있으면 맀우 도움이 λ©λ‹ˆλ‹€. mailoutbox is mail.outbox 인 경우 λˆ„κ΅°κ°€κ°€ μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜κΈ° 전에 보낼 νŽΈμ§€ν•¨μ„ μ§€μšΈ 수 있으며 이 μ½”λ“œλŠ” νŠΉμ • 수의 이메일을 μƒμ„±ν•˜λ„λ‘ λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€(IEλŠ” 1 이메일).

이것이 이 λ¬Έμ œμ™€ 관련이 μžˆλŠ”μ§€ ν™•μ‹€ν•˜μ§€ μ•Šμ§€λ§Œ mailoutbox κ³ μ • μž₯μΉ˜κ°€ λ‚΄ ν…ŒμŠ€νŠΈμ—μ„œ mail.outbox 의 λ™μž‘κ³Ό μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 보낸 이메일을 μˆ˜μ§‘ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

(Pdb) mail.outbox
[<django.core.mail.message.EmailMultiAlternatives object at 0x7f6d864cb198>]
(Pdb) mailoutbox
[]
(Pdb)

이것이 이 λ¬Έμ œμ™€ 관련이 μžˆλŠ”μ§€ ν™•μ‹€ν•˜μ§€ μ•Šμ§€λ§Œ mailoutbox κ³ μ • μž₯μΉ˜κ°€ λ‚΄ ν…ŒμŠ€νŠΈμ—μ„œ mail.outbox 의 λ™μž‘κ³Ό μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 보낸 이메일을 μˆ˜μ§‘ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

(Pdb) mail.outbox
[<django.core.mail.message.EmailMultiAlternatives object at 0x7f6d864cb198>]
(Pdb) mailoutbox
[]
(Pdb)

같은.
mail.outbox μ—λŠ” λ©”μ‹œμ§€κ°€ ν¬ν•¨λ˜μ–΄ 있고 mailoutbox λŠ” λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
pytest-django 버전은 3.9.0μž…λ‹ˆλ‹€.

같은 λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 κ³ μ • μž₯치λ₯Ό μ‚¬μš©ν•˜λ©΄ μ‹€νŒ¨ν•©λ‹ˆλ‹€.

def test_send_foo_mail(mailoutbox, user_client, foo):

λ‹€μŒκ³Ό 같이 μž‘λ™ν•©λ‹ˆλ‹€.

def test_send_foo_mail(user_client, foo, mailoutbox):

그것을 λ°œκ²¬ν•˜λŠ” 데 λ§Žμ€ μ‹œκ°„μ΄ κ±Έλ ΈμŠ΅λ‹ˆλ‹€. 이 문제λ₯Ό λ””λ²„κΉ…ν•˜λŠ” 데 도움이 λ˜μ—ˆμœΌλ©΄ ν•©λ‹ˆλ‹€. κ΅¬ν˜„μ„ λ³΄μ•˜μ§€λ§Œ 이것을 λ””λ²„κΉ…ν•˜λŠ” 방법에 λŒ€ν•œ λ‹¨μ„œκ°€ μ—†μŠ΅λ‹ˆλ‹€.

κ΄€λ ¨: https://stackoverflow.com/questions/66846621/mailoutbox-works-only-if-last-fixture

이전 ν•΄μ„€μžμ™€ λ™μΌν•œ 행동을 κ²½ν—˜ν•©λ‹ˆλ‹€. λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•˜λ©΄ μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

def test_command_and_email(
    mailoutbox, argument, time_machine, mocker 
):

κ·ΈλŸ¬λ‚˜ λ§ˆμ§€λ§‰ μœ„μΉ˜μ— λ„£μœΌλ©΄ λͺ¨λ“  것이 μ˜ˆμƒλŒ€λ‘œ μž‘λ™ν•©λ‹ˆλ‹€.

def test_command_and_email(
    argument, time_machine, mocker, mailoutbox
):

인수 argument λ˜ν•œ μ‚¬μš©ν•˜λŠ”κΈ°κ΅¬μ΄λ‹€ db I가이 문제의 주된 이유 κ°™μ•„μš”, κ·Έλž˜μ„œ κ³ μ •ν•©λ‹ˆλ‹€.
pytets-django 4.3.0

이 νŽ˜μ΄μ§€κ°€ 도움이 λ˜μ—ˆλ‚˜μš”?
0 / 5 - 0 λ“±κΈ‰