จะทดสอบหน่วยด้วยการตั้งค่าต่างๆใน Django ได้อย่างไร?


117

มีกลไกง่ายๆในการลบล้างการตั้งค่า Django สำหรับการทดสอบหน่วยหรือไม่? ฉันมีผู้จัดการในโมเดลของฉันที่ส่งคืนอ็อบเจ็กต์ล่าสุดจำนวนหนึ่ง จำนวนออบเจ็กต์ที่ส่งคืนถูกกำหนดโดยการตั้งค่า NUM_LATEST

สิ่งนี้อาจทำให้การทดสอบของฉันล้มเหลวหากมีคนเปลี่ยนการตั้งค่า ฉันจะลบล้างการตั้งค่าsetUp()และเรียกคืนในภายหลังได้tearDown()อย่างไร หากเป็นไปไม่ได้มีวิธีใดบ้างที่ฉันสามารถแก้ไขวิธีการหรือล้อเลียนการตั้งค่าได้

แก้ไข:นี่คือรหัสผู้จัดการของฉัน:

class LatestManager(models.Manager):
    """
    Returns a specific number of the most recent public Articles as defined by 
    the NEWS_LATEST_MAX setting.
    """
    def get_query_set(self):
        num_latest = getattr(settings, 'NEWS_NUM_LATEST', 10)
        return super(LatestManager, self).get_query_set().filter(is_public=True)[:num_latest]

ผู้จัดการใช้settings.NEWS_LATEST_MAXเพื่อแบ่งชุดแบบสอบถาม getattr()จะถูกใช้เพียงเพื่อให้เริ่มต้นควรตั้งค่าได้อยู่


@Anto - คุณสามารถอธิบายเหตุผลหรือให้คำตอบที่ดีกว่า
ผู้ใช้

มันเปลี่ยนไปในระหว่างนี้ อดีตที่ยอมรับคืออันนี้ ;)
Anto

คำตอบ:


164

แก้ไข: คำตอบนี้ใช้ถ้าคุณต้องการที่จะเปลี่ยนการตั้งค่าสำหรับขนาดเล็กจำนวนเฉพาะการทดสอบ

ตั้งแต่ Django 1.4 มีวิธีลบล้างการตั้งค่าระหว่างการทดสอบ: https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

TestCase จะมีตัวจัดการบริบท self.settings และยังมีมัณฑนากร @override_settings ที่สามารถนำไปใช้กับวิธีการทดสอบหรือคลาสย่อย TestCase ทั้งหมด

คุณสมบัติเหล่านี้ยังไม่มีใน Django 1.3

หากคุณต้องการเปลี่ยนการตั้งค่าสำหรับการทดสอบทั้งหมดของคุณคุณจะต้องสร้างไฟล์การตั้งค่าแยกต่างหากสำหรับการทดสอบซึ่งสามารถโหลดและลบล้างการตั้งค่าจากไฟล์การตั้งค่าหลักของคุณ มีแนวทางที่ดีหลายประการในคำตอบอื่น ๆ ฉันได้เห็นรูปแบบที่ประสบความสำเร็จทั้งของ hspanderและDmitrii ของวิธีการ


4
ฉันจะบอกว่านี่เป็นวิธีที่ดีที่สุดในการทำสิ่งนี้ใน Django 1.4+
Michael Mior

คุณจะเข้าถึงการตั้งค่านั้นจากภายในการทดสอบในภายหลังได้อย่างไร สิ่งที่ดีที่สุดที่ฉันพบคือบางอย่างself.settings().wrapped.MEDIA_ROOTแต่มันแย่มาก
mlissner

2
Django เวอร์ชันใหม่กว่ามีตัวจัดการบริบทเฉพาะสำหรับสิ่งนี้: docs.djangoproject.com/th/1.8/topics/testing/tools/…
Akhorus

สิ่งที่ฉันชอบ: @modify_settings(MIDDLEWARE_CLASSES=...(ขอบคุณสำหรับคำตอบนี้)
guettli

44

คุณสามารถทำอะไรก็ได้ที่ต้องการในUnitTestคลาสย่อยรวมถึงการตั้งค่าและการอ่านคุณสมบัติของอินสแตนซ์:

from django.conf import settings

class MyTest(unittest.TestCase):
   def setUp(self):
       self.old_setting = settings.NUM_LATEST
       settings.NUM_LATEST = 5 # value tested against in the TestCase

   def tearDown(self):
       settings.NUM_LATEST = self.old_setting

เนื่องจากกรณีการทดสอบ django เรียกใช้เธรดเดี่ยวฉันจึงสงสัยว่าจะมีอะไรแก้ไขค่า NUM_LATEST ได้อีกหรือไม่ หาก "อย่างอื่น" ถูกกระตุ้นโดยขั้นตอนการทดสอบของคุณแสดงว่าฉันไม่แน่ใจว่าการปะลิงจำนวนเท่าใดก็ตามจะบันทึกการทดสอบโดยไม่ทำให้ความจริงของการทดสอบเป็นโมฆะ


ตัวอย่างของคุณได้ผล นี่เป็นการเปิดหูเปิดตาในแง่ของขอบเขตของการทดสอบหน่วยและวิธีการตั้งค่าในไฟล์ทดสอบเผยแพร่ผ่าน call stack
Soviut

สิ่งนี้ใช้ไม่ได้กับsettings.TEMPLATE_LOADERS... ดังนั้นนี่ไม่ใช่วิธีทั่วไปอย่างน้อยการตั้งค่าหรือ Django จะไม่ถูกโหลดซ้ำหรืออะไรก็ตามด้วยเคล็ดลับนี้
Ciantic

1
นี่เป็นตัวอย่างที่ดีสำหรับ Django เวอร์ชันเก่ากว่า 1.4 สำหรับ> = 1.4 คำตอบstackoverflow.com/a/6415129/190127ถูกต้องมากขึ้น
Oduvan

ใช้docs.djangoproject.com/en/dev/topics/testing/tools/…การแพทช์ ด้วยsetUpและ tearDown เช่นนี้เป็นวิธีที่ดีในการทำแบบทดสอบที่เปราะบางจริงๆซึ่งมีรายละเอียดมากกว่าที่ควรจะเป็น หากคุณต้องการแก้ไขบางอย่างเช่นนี้ให้ใช้บางอย่างเช่น flexmock
ฟัซซี่วาฟเฟิล

"เนื่องจากกรณีทดสอบ django เรียกใช้เธรดเดี่ยว": ซึ่งไม่เป็นเช่นนั้นใน Django 1.9 อีกต่อไป
Wtower

22

แม้ว่าการลบล้างการกำหนดค่าการตั้งค่าบนรันไทม์อาจช่วยได้ แต่ในความคิดของฉันคุณควรสร้างไฟล์แยกต่างหากสำหรับการทดสอบ สิ่งนี้ช่วยประหยัดการกำหนดค่าจำนวนมากสำหรับการทดสอบและสิ่งนี้จะทำให้แน่ใจได้ว่าคุณจะไม่ต้องทำสิ่งที่กลับไม่ได้ (เช่นการล้างฐานข้อมูลการจัดเตรียม)

สมมติว่าไฟล์ทดสอบของคุณมีอยู่ใน "my_project / test_settings.py" ให้เพิ่ม

settings = 'my_project.test_settings' if 'test' in sys.argv else 'my_project.settings'

ใน Manage.py ของคุณ เพื่อให้แน่ใจว่าเมื่อคุณเรียกใช้python manage.py testคุณใช้ test_settings เท่านั้น หากคุณใช้ไคลเอนต์ทดสอบอื่น ๆ เช่น pytest คุณสามารถเพิ่มสิ่งนี้ลงใน pytest.ini ได้อย่างง่ายดาย


2
ฉันคิดว่านี่เป็นทางออกที่ดีสำหรับฉัน ฉันมีการทดสอบและโค้ดมากเกินไปซึ่งใช้แคช มันจะยากสำหรับฉันที่จะลบล้างการตั้งค่าทีละรายการ ฉันจะสร้างไฟล์กำหนดค่าสองไฟล์และกำหนดว่าจะใช้ไฟล์ใด คำตอบของ MicroPyramid สามารถใช้ได้เช่นกัน แต่จะเป็นอันตรายถ้าฉันลืมเพิ่มพารามิเตอร์การตั้งค่าหนึ่งครั้ง
ramwin

22

คุณสามารถผ่าน--settingsตัวเลือกเมื่อเรียกใช้การทดสอบ

python manage.py test --settings=mysite.settings_local

หยุดค้นหาแอปที่อยู่ใน settings.dev ซึ่งเป็นส่วนขยายของ settings.base
holms

4
ฉันคิดว่ามันจะเป็นอันตรายหากมีคนลืมเพิ่มพารามิเตอร์การตั้งค่าเพียงครั้งเดียว
ramwin

20

อัปเดต : โซลูชันด้านล่างนี้จำเป็นสำหรับ Django 1.3.x และรุ่นก่อนหน้าเท่านั้น สำหรับ> 1.4 เห็นคำตอบของ slinkp

หากคุณเปลี่ยนการตั้งค่าบ่อยๆในการทดสอบและใช้ Python ≥2.5สิ่งนี้ก็มีประโยชน์เช่นกัน:

from contextlib import contextmanager

class SettingDoesNotExist:
    pass

@contextmanager
def patch_settings(**kwargs):
    from django.conf import settings
    old_settings = []
    for key, new_value in kwargs.items():
        old_value = getattr(settings, key, SettingDoesNotExist)
        old_settings.append((key, old_value))
        setattr(settings, key, new_value)
    yield
    for key, old_value in old_settings:
        if old_value is SettingDoesNotExist:
            delattr(settings, key)
        else:
            setattr(settings, key, old_value)

จากนั้นคุณสามารถทำได้:

with patch_settings(MY_SETTING='my value', OTHER_SETTING='other value'):
    do_my_tests()

นี่เป็นทางออกที่ยอดเยี่ยมจริงๆ ด้วยเหตุผลบางประการการตั้งค่าของฉันทำงานไม่ถูกต้องในการทดสอบหน่วย ทางออกที่สวยงามมากขอบคุณสำหรับการแบ่งปัน
Tomas

ฉันใช้รหัสนี้ แต่ฉันมีปัญหาเกี่ยวกับความล้มเหลวในการทดสอบแบบเรียงซ้อนเนื่องจากการตั้งค่าจะไม่ถูกเปลี่ยนกลับหากการทดสอบที่เป็นปัญหาล้มเหลว เพื่อแก้ไขปัญหานี้ฉันได้เพิ่ม try / ในที่สุดรอบ ๆyieldคำสั่งโดยมีส่วนสุดท้ายของฟังก์ชันที่อยู่ในfinallyบล็อกเพื่อให้การตั้งค่าถูกย้อนกลับเสมอ
Dustin Rasener

ฉันจะแก้ไขคำตอบสำหรับลูกหลาน ฉันหวังว่าฉันจะทำถูกต้อง! :)
Dustin Rasener

11

@override_settings จะดีมากหากคุณไม่มีความแตกต่างมากนักระหว่างการกำหนดค่าสภาพแวดล้อมการผลิตและการทดสอบ

ในกรณีอื่นคุณควรมีไฟล์การตั้งค่าที่แตกต่างกัน ในกรณีนี้โครงการของคุณจะมีลักษณะดังนี้:

your_project
    your_app
        ...
    settings
        __init__.py
        base.py
        dev.py
        test.py
        production.py
    manage.py

ดังนั้นคุณต้องมีการตั้งค่าส่วนใหญ่base.pyในไฟล์อื่น ๆ คุณต้องนำเข้าทุกอย่างจากที่นั่นและแทนที่ตัวเลือกบางอย่าง นี่คือลักษณะของtest.pyไฟล์ของคุณ:

from .base import *

DEBUG = False

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'app_db_test'
    }
}

PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

LOGGING = {}

จากนั้นคุณต้องระบุ--settingsตัวเลือกเป็นในคำตอบ @MicroPyramid หรือระบุDJANGO_SETTINGS_MODULEตัวแปรสภาพแวดล้อมจากนั้นคุณสามารถเรียกใช้การทดสอบของคุณ:

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 

สวัสดี . Dmitrii ขอบคุณสำหรับคำตอบของคุณฉันมีกรณีเดียวกันกับคำตอบนี้ แต่ฉันต้องการคำแนะนำเพิ่มเติมว่าแอปจะรู้ได้อย่างไรสภาพแวดล้อมที่เราอยู่(การทดสอบหรือการผลิต)ดูสาขาของฉันตรวจสอบ repo github.com/andela/ah-backend-iroquois/tree/develop/authors ของฉันฉันจะจัดการตรรกะนั้นอย่างไร
Lutaaya Huzaifah Idris

เนื่องจากฉันใช้nosetestsเพื่อเรียกใช้การทดสอบตอนนี้จะรันอย่างไรในสภาพแวดล้อมการทดสอบไม่ได้อยู่ในสภาพแวดล้อมการพัฒนา
Lutaaya Huzaifah Idris

3

พบสิ่งนี้ในขณะที่พยายามแก้ไขบางหลัก ... เพื่อความสมบูรณ์ฉันต้องการพูดถึงว่าถ้าคุณจะแก้ไขการตั้งค่าเมื่อใช้หลักคุณควรทำก่อนที่จะนำเข้าสิ่งอื่น ...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

>>> # Your other imports
>>> from django.core.paginator import Paginator
>>> # etc

3

สำหรับผู้ใช้pytest

ปัญหาใหญ่ที่สุดคือ:

  • override_settings ใช้ไม่ได้กับ pytest
  • Subclassing Django TestCaseจะทำให้มันใช้งานได้ แต่คุณไม่สามารถใช้การแข่งขัน pytest ได้

การแก้ปัญหาคือการใช้settingsโคมเอกสารที่นี่

ตัวอย่าง

def test_with_specific_settings(settings):
    settings.DEBUG = False
    settings.MIDDLEWARE = []
    ..

และในกรณีที่คุณต้องการอัปเดตหลายช่อง

def override_settings(settings, kwargs):
    for k, v in kwargs.items():
        setattr(settings, k, v)


new_settings = dict(
    DEBUG=True,
    INSTALLED_APPS=[],
)


def test_with_specific_settings(settings):
    override_settings(settings, new_settings)

3

คุณสามารถลบล้างการตั้งค่าแม้กระทั่งสำหรับฟังก์ชันทดสอบเดียว

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

    @override_settings(SOME_SETTING="some_value")
    def test_some_function():
        

หรือคุณสามารถลบล้างการตั้งค่าสำหรับแต่ละฟังก์ชันในคลาส

@override_settings(SOME_SETTING="some_value")
class SomeTestCase(TestCase):

    def test_some_function():
        

1

ฉันใช้ pytest

ฉันจัดการเพื่อแก้ปัญหานี้ด้วยวิธีต่อไปนี้:

import django    
import app.setting
import modules.that.use.setting

# do some stuff with default setting
setting.VALUE = "some value"
django.setup()
import importlib
importlib.reload(app.settings)
importlib.reload(modules.that.use.setting)
# do some stuff with settings new value

1

คุณสามารถลบล้างการตั้งค่าในการทดสอบได้ด้วยวิธีนี้:

from django.test import TestCase, override_settings

test_settings = override_settings(
    DEFAULT_FILE_STORAGE='django.core.files.storage.FileSystemStorage',
    PASSWORD_HASHERS=(
        'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
    )
)


@test_settings
class SomeTestCase(TestCase):
    """Your test cases in this class"""

test_settingsและถ้าคุณต้องการตั้งค่าเดียวกันเหล่านี้ในแฟ้มอื่นคุณก็สามารถนำเข้าโดยตรง


0

หากคุณมีไฟล์ทดสอบหลายไฟล์อยู่ในไดเร็กทอรีย่อย (แพ็กเกจ python) คุณสามารถแทนที่การตั้งค่าสำหรับไฟล์เหล่านี้ทั้งหมดตามเงื่อนไขการมีอยู่ของสตริง 'test' ใน sys.argv

app
  tests
    __init__.py
    test_forms.py
    test_models.py

__init__.py:

import sys
from project import settings

if 'test' in sys.argv:
    NEW_SETTINGS = {
        'setting_name': value,
        'another_setting_name': another_value
    }
    settings.__dict__.update(NEW_SETTINGS)

ไม่ใช่แนวทางที่ดีที่สุด ใช้เพื่อเปลี่ยนนายหน้าขึ้นฉ่ายจาก Redis เป็น Memory


0

ฉันสร้างไฟล์ settings_test.py ใหม่ซึ่งจะนำเข้าทุกอย่างจากไฟล์ settings.py และแก้ไขสิ่งที่แตกต่างออกไปเพื่อวัตถุประสงค์ในการทดสอบ ในกรณีของฉันฉันต้องการใช้ที่เก็บข้อมูลบนคลาวด์อื่นเมื่อทำการทดสอบ ใส่คำอธิบายภาพที่นี่

settings_test.py:

from project1.settings import *
import os

CLOUD_STORAGE_BUCKET = 'bucket_name_for_testing'

Manage.py:

def main():

    # use seperate settings.py for tests
    if 'test' in sys.argv:
        print('using settings_test.py')
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project1.settings_test')
    else:
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project1.settings')

    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.