Làm thế nào để Kiểm tra đơn vị với các cài đặt khác nhau trong Django?


116

Có cơ chế đơn giản nào để ghi đè cài đặt Django cho bài kiểm tra đơn vị không? Tôi có một trình quản lý trên một trong các mô hình của tôi trả về một số lượng cụ thể các đối tượng mới nhất. Số lượng đối tượng mà nó trả về được xác định bởi một cài đặt NUM_LATEST.

Điều này có khả năng làm cho các thử nghiệm của tôi không thành công nếu ai đó thay đổi cài đặt. Làm cách nào để tôi có thể ghi đè cài đặt trên setUp()và sau đó khôi phục lại chúng tearDown()? Nếu điều đó không thể thực hiện được, có cách nào đó để tôi có thể vá phương pháp hoặc mô phỏng cài đặt không?

CHỈNH SỬA: Đây là mã người quản lý của tôi:

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]

Người quản lý sử dụng settings.NEWS_LATEST_MAXđể cắt bộ truy vấn. Chỉ getattr()được sử dụng để cung cấp một mặc định nếu cài đặt không tồn tại.


@Anto - bạn có thể giải thích tại sao hoặc cung cấp câu trả lời tốt hơn không?
người dùng

Nó đã thay đổi trong khi chờ đợi; cựu một chấp nhận được cái này ;)
Anto

Câu trả lời:


163

EDIT: Câu trả lời này áp dụng nếu bạn muốn thay đổi cài đặt cho một nhỏ số lượng cụ thể kiểm tra.

Kể từ Django 1.4, có nhiều cách để ghi đè cài đặt trong quá trình kiểm tra: https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

TestCase sẽ có trình quản lý ngữ cảnh self.settings và cũng sẽ có trình trang trí @override_settings có thể được áp dụng cho phương pháp thử nghiệm hoặc toàn bộ lớp con TestCase.

Các tính năng này chưa tồn tại trong Django 1.3.

Nếu bạn muốn thay đổi cài đặt cho tất cả các thử nghiệm của mình, bạn sẽ muốn tạo một tệp cài đặt riêng để kiểm tra, tệp này có thể tải và ghi đè cài đặt từ tệp cài đặt chính của bạn. Có một số cách tiếp cận tốt cho điều này trong các câu trả lời khác; Tôi đã thấy các biến thể thành công trên cả phương pháp tiếp cận của hspanderdmitrii .


4
Tôi muốn nói rằng đây là cách tốt nhất để làm điều này hiện tại trong Django 1.4+
Michael Mior

Làm cách nào để bạn truy cập cài đặt đó sau này từ trong các thử nghiệm? Điều tốt nhất tôi đã tìm thấy là một cái gì đó giống như self.settings().wrapped.MEDIA_ROOT, nhưng điều đó khá khủng khiếp.
mlissner

2
Các phiên bản mới hơn của Django có trình quản lý ngữ cảnh cụ thể cho điều này: docs.djangoproject.com/en/1.8/topics/testing/tools/…
Akhorus

Ưa thích của tôi: @modify_settings(MIDDLEWARE_CLASSES=...(cảm ơn bạn cho câu trả lời này)
guettli

44

Bạn có thể làm bất cứ điều gì bạn thích đối với UnitTestlớp con, bao gồm thiết lập và đọc các thuộc tính cá thể:

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

Tuy nhiên, vì các trường hợp thử nghiệm django chạy đơn luồng, tôi tò mò về điều gì khác có thể đang sửa đổi giá trị NUM_LATEST? Nếu "cái gì đó khác" được kích hoạt bởi quy trình kiểm tra của bạn, thì tôi không chắc bất kỳ số lượng bản vá khỉ nào sẽ lưu bài kiểm tra mà không làm mất hiệu lực của chính các bài kiểm tra.


Ví dụ của bạn đã hoạt động. Điều này đã được mở rộng về phạm vi thử nghiệm đơn vị và cách cài đặt trong tệp thử nghiệm truyền xuống thông qua ngăn xếp cuộc gọi.
Soviut

Điều này không hoạt động với settings.TEMPLATE_LOADERS... Vì vậy, đây không phải là cách chung ít nhất, cài đặt hoặc Django không được tải lại hoặc bất cứ điều gì với thủ thuật này.
Ciantic

1
đây là ví dụ điển hình cho phiên bản Django cũ hơn 1.4. Đối với> = 1,4 Câu trả lời stackoverflow.com/a/6415129/190127 chính xác hơn
Oduvan

Sử dụng docs.djangoproject.com/en/dev/topics/testing/tools/… Việc vá lỗi bằng setUp và drawDown như thế này là một cách tuyệt vời để thực hiện các bài kiểm tra thực sự mỏng manh, dài dòng hơn mức cần thiết. Nếu bạn cần vá một cái gì đó như thế này, hãy sử dụng một cái gì đó như flexmock.
mờ-waffle

"Vì các trường hợp thử nghiệm django chạy đơn luồng": điều này không còn xảy ra trong Django 1.9.
Wtower

22

Mặc dù việc ghi đè cấu hình cài đặt trong thời gian chạy có thể hữu ích, nhưng theo tôi bạn nên tạo một tệp riêng để thử nghiệm. Điều này tiết kiệm rất nhiều cấu hình để thử nghiệm và điều này sẽ đảm bảo rằng bạn sẽ không bao giờ làm điều gì đó không thể thay đổi được (như dọn dẹp cơ sở dữ liệu giai đoạn).

Giả sử tệp thử nghiệm của bạn tồn tại trong 'my_project / test_settings.py', thêm

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

trong management.py của bạn. Điều này sẽ đảm bảo rằng khi bạn chạy, python manage.py testbạn chỉ sử dụng test_settings. Nếu bạn đang sử dụng một số ứng dụng thử nghiệm khác như pytest, bạn có thể dễ dàng thêm nó vào pytest.ini


2
Tôi nghĩ rằng đây là một giải pháp tốt cho tôi. Tôi có quá nhiều bài kiểm tra và mã đang sử dụng bộ nhớ cache. Sẽ rất khó để tôi ghi đè từng cài đặt một. Tôi sẽ tạo hai tệp cấu hình và xác định tệp nào sẽ sử dụng. Câu trả lời của MicroPyramid cũng có sẵn, nhưng sẽ rất nguy hiểm nếu tôi quên thêm các thông số cài đặt một lần.
ramwin

22

Bạn có thể vượt qua --settingstùy chọn khi chạy thử nghiệm

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

nó dừng lại để tìm các ứng dụng nằm trong settings.dev, là phần mở rộng của settings.base
holms

4
Tôi nghĩ sẽ rất nguy hiểm nếu ai đó quên thêm các thông số cài đặt một lần.
ramwin

20

Cập nhật : giải pháp bên dưới chỉ cần thiết trên Django 1.3.x trở về trước. Đối với> 1.4, hãy xem câu trả lời của slinkp .

Nếu bạn thường xuyên thay đổi cài đặt trong các thử nghiệm của mình và sử dụng Python ≥2.5, điều này cũng rất hữu ích:

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)

Sau đó, bạn có thể làm:

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

Đây thực sự là giải pháp tuyệt vời. Vì một số lý do cài đặt của tôi không hoạt động bình thường trong các bài kiểm tra đơn vị. Giải pháp rất thanh lịch, cảm ơn vì đã chia sẻ.
Tomas

Tôi đang sử dụng mã này, nhưng tôi đã gặp sự cố với lỗi kiểm tra xếp tầng, vì cài đặt sẽ không được hoàn nguyên nếu kiểm tra được đề cập không thành công. Để giải quyết vấn đề này, tôi đã thêm một thử / cuối cùng xung quanh yieldcâu lệnh, với phần cuối cùng của hàm chứa trongfinally khối, để các cài đặt luôn được hoàn nguyên.
Dustin Rasener

Tôi sẽ chỉnh sửa câu trả lời cho hậu thế. Tôi hy vọng tôi đang làm điều này đúng! :)
Dustin Rasener

11

@override_settings thật tuyệt nếu bạn không có nhiều khác biệt giữa cấu hình môi trường sản xuất và thử nghiệm.

Trong trường hợp khác, tốt hơn bạn nên có các tệp cài đặt khác nhau. Trong trường hợp này, dự án của bạn sẽ giống như sau:

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

Vì vậy, bạn cần có hầu hết các cài đặt của mình base.pyvà sau đó trong các tệp khác, bạn cần nhập tất cả mọi thứ từ đó và ghi đè một số tùy chọn. Đây là test.pytệp của bạn sẽ trông như thế nào:

from .base import *

DEBUG = False

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

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

LOGGING = {}

Và sau đó bạn cần chỉ định --settings tùy chọn như trong câu trả lời @MicroPyramid hoặc chỉ định DJANGO_SETTINGS_MODULEbiến môi trường và sau đó bạn có thể chạy thử nghiệm của mình:

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 

Xin chào . Dmitrii, cảm ơn câu trả lời của bạn, tôi đang gặp trường hợp tương tự với câu trả lời này, nhưng tôi muốn được hướng dẫn thêm về cách ứng dụng sẽ biết, môi trường chúng tôi đang ở (thử nghiệm hoặc sản xuất) , hãy xem chi nhánh của tôi, hãy xem repo github.com/andela/ah-backend-iroquois/tree/develop/authors của tôi , như tôi sẽ xử lý logic đó như thế nào?
Lutaaya Huzaifah Idris

Bởi vì tôi sử dụng nosetests để chạy thử nghiệm, bây giờ như thế nào sẽ này được chạy ?, trong môi trường thử nghiệm không phải trong môi trường phát triển
Lutaaya Huzaifah Idris

3

Tìm thấy điều này khi cố gắng sửa một số học thuyết ... Để hoàn thiện, tôi muốn đề cập rằng nếu bạn định sửa đổi cài đặt khi sử dụng học thuyết, bạn nên thực hiện trước khi nhập bất kỳ thứ gì khác ...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

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

3

Đối với người dùng pytest .

Vấn đề lớn nhất là:

  • override_settings không hoạt động với pytest.
  • Phân lớp con của Django TestCasesẽ làm cho nó hoạt động nhưng sau đó bạn không thể sử dụng đồ đạc pytest.

Giải pháp là sử dụng settingsvật cố định được ghi ở đây .

Thí dụ

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

Và trong trường hợp bạn cần cập nhật nhiều trường

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)

2

Bạn có thể ghi đè cài đặt ngay cả đối với một chức năng kiểm tra.

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

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

hoặc bạn có thể ghi đè cài đặt cho từng chức năng trong lớp.

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

    def test_some_function():
        

1

Tôi đang sử dụng pytest.

Tôi đã giải quyết vấn đề này theo cách sau:

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

Bạn có thể ghi đè cài đặt trong thử nghiệm theo cách này:

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"""

Và nếu bạn cần những cài đặt tương tự này trong một tệp khác, bạn có thể nhập trực tiếp test_settings.


0

Nếu bạn có nhiều tệp thử nghiệm được đặt trong một thư mục con (gói python), bạn có thể ghi đè cài đặt cho tất cả các tệp này dựa trên điều kiện hiện diện của chuỗi 'thử nghiệm' trong 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)

Không phải là cách tiếp cận tốt nhất. Đã sử dụng nó để thay đổi nhà môi giới Cần tây từ Redis sang Memory.


0

Tôi đã tạo một tệp settings_test.py mới sẽ nhập mọi thứ từ tệp settings.py và sửa đổi bất kỳ điều gì khác cho mục đích thử nghiệm. Trong trường hợp của tôi, tôi muốn sử dụng một nhóm lưu trữ đám mây khác khi thử nghiệm. nhập mô tả hình ảnh ở đây

settings_test.py:

from project1.settings import *
import os

CLOUD_STORAGE_BUCKET = 'bucket_name_for_testing'

management.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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.