Bagaimana menguji Unit dengan pengaturan berbeda di Django?

116

Apakah ada mekanisme sederhana untuk menimpa pengaturan Django untuk pengujian unit? Saya memiliki manajer di salah satu model saya yang mengembalikan sejumlah objek terbaru. Jumlah objek yang dikembalikan ditentukan oleh pengaturan NUM_LATEST.

Ini berpotensi membuat pengujian saya gagal jika seseorang mengubah pengaturan. Bagaimana saya bisa mengganti pengaturan setUp()dan kemudian mengembalikannya tearDown()? Jika itu tidak memungkinkan, adakah cara agar saya dapat menambal metode atau meniru pengaturannya?

EDIT: Ini kode manajer saya:

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]

Manajer menggunakan settings.NEWS_LATEST_MAXuntuk memotong queryset. Ini getattr()hanya digunakan untuk memberikan default jika pengaturan tidak ada.

Soviut
sumber
@Anto - dapatkah Anda menjelaskan mengapa atau memberikan jawaban yang lebih baik?
pengguna
Itu berubah sementara itu; yang pertama diterima adalah yang ini ;)
Anto

Jawaban:

163

EDIT: Jawaban ini berlaku jika Anda ingin mengubah pengaturan untuk kecil jumlah tertentu tes.

Sejak Django 1.4, terdapat cara untuk mengganti pengaturan selama pengujian: https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

TestCase akan memiliki pengelola konteks self.settings, dan juga akan ada dekorator @override_settings yang dapat diterapkan ke metode pengujian atau seluruh subkelas TestCase.

Fitur-fitur ini belum ada di Django 1.3.

Jika Anda ingin mengubah pengaturan untuk semua pengujian Anda, Anda akan ingin membuat file pengaturan terpisah untuk pengujian, yang dapat memuat dan mengganti pengaturan dari file pengaturan utama Anda. Ada beberapa pendekatan bagus untuk ini di jawaban lain; Saya telah melihat variasi sukses pada kedua ini hspander dan Dmitrii ini pendekatan.

slinkp
sumber
4
Saya akan mengatakan ini adalah cara terbaik untuk melakukan ini sekarang di Django 1.4+
Michael Mior
Bagaimana Anda nanti mengakses pengaturan itu dari dalam pengujian? Terbaik yang saya temukan adalah sesuatu seperti self.settings().wrapped.MEDIA_ROOT, tapi itu sangat mengerikan.
mlissner
2
Versi terbaru Django mempunyai pengelola konteks khusus untuk ini: docs.djangoproject.com/en/1.8/topics/testing/tools/…
Akhorus
Favorit saya: @modify_settings(MIDDLEWARE_CLASSES=...(terima kasih atas jawaban ini)
guettli
44

Anda dapat melakukan apa pun yang Anda suka pada UnitTestsubkelas tersebut, termasuk menyetel dan membaca properti instance:

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

Karena kasus pengujian django menjalankan utas tunggal, bagaimanapun, saya ingin tahu tentang apa lagi yang mungkin mengubah nilai NUM_LATEST? Jika "sesuatu yang lain" itu dipicu oleh rutinitas pengujian Anda, maka saya tidak yakin jumlah monkey patching akan menyelamatkan pengujian tanpa membatalkan kebenaran pengujian itu sendiri.

Jarret Hardie
sumber
Teladan Anda berhasil. Ini telah menjadi pembuka mata dalam hal cakupan pengujian unit dan bagaimana setelan dalam file pengujian menyebar melalui tumpukan panggilan.
Soviut
Ini tidak bekerja dengan settings.TEMPLATE_LOADERS... Jadi ini setidaknya bukan cara umum, pengaturan atau Django tidak dimuat ulang atau apapun dengan trik ini.
Ciantic
1
ini adalah contoh yang bagus untuk versi Django yang lebih lama dari 1.4. Untuk> = 1.4 jawaban stackoverflow.com/a/6415129/190127 lebih benar
Oduvan
Gunakan docs.djangoproject.com/en/dev/topics/testing/tools/… Menambal dengan setUp dan tearDown seperti ini adalah cara yang bagus untuk membuat pengujian yang sangat rapuh yang lebih bertele-tele daripada yang seharusnya. Jika Anda perlu menambal sesuatu seperti ini, gunakan sesuatu seperti flexmock.
fuzzy-waffle
"Sejak kasus pengujian django menjalankan utas tunggal": yang tidak lagi kasus di Django 1.9.
Wtower
22

Meskipun mengesampingkan konfigurasi pengaturan pada runtime mungkin membantu, menurut saya Anda harus membuat file terpisah untuk pengujian. Ini menghemat banyak konfigurasi untuk pengujian dan ini akan memastikan bahwa Anda tidak akan pernah melakukan sesuatu yang tidak dapat diubah (seperti membersihkan database pementasan).

Misalkan file pengujian Anda ada di 'my_project / test_settings.py', tambahkan

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

di manage.py Anda. Ini akan memastikan bahwa ketika Anda menjalankan python manage.py testAnda hanya menggunakan test_settings. Jika Anda menggunakan beberapa klien pengujian lain seperti pytest, Anda dapat dengan mudah menambahkan ini ke pytest.ini

hspandher
sumber
2
Saya rasa ini adalah solusi yang bagus untuk saya. Saya memiliki terlalu banyak tes dan kode yang menggunakan cache. Akan sulit bagi saya untuk mengganti pengaturan satu per satu. Saya akan membuat dua file konfigurasi dan menentukan mana yang akan digunakan. Jawaban MicroPyramid juga tersedia, tetapi akan berbahaya jika saya lupa menambahkan parameter pengaturan sekali.
ramwin
22

Anda dapat memberikan --settingsopsi saat menjalankan pengujian

python manage.py test --settings=mysite.settings_local
MicroPyramid
sumber
itu berhenti untuk menemukan aplikasi yang terletak di settings.dev yang merupakan ekstensi dari settings.base
holms
4
Menurut saya akan berbahaya jika seseorang lupa menambahkan parameter pengaturan sekali.
ramwin
20

Pemutakhiran : solusi di bawah ini hanya dibutuhkan pada Django 1.3.x dan sebelumnya. Untuk> 1.4 lihat jawaban slinkp .

Jika Anda sering mengubah pengaturan dalam pengujian Anda dan menggunakan Python ≥2.5, ini juga berguna:

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)

Kemudian Anda dapat melakukan:

with patch_settings(MY_SETTING='my value', OTHER_SETTING='other value'):
    do_my_tests()
akaihola
sumber
Ini solusi yang sangat keren. Untuk beberapa alasan, pengaturan saya tidak berfungsi dengan benar dalam pengujian unit. Solusi yang sangat elegan, terima kasih telah berbagi.
Tomas
Saya menggunakan kode ini, tetapi saya mengalami masalah dengan kegagalan pengujian berjenjang, karena pengaturan tidak akan dikembalikan jika pengujian tersebut gagal. Untuk mengatasi ini, saya menambahkan percobaan / akhirnya di sekitar yieldpernyataan, dengan bagian terakhir dari fungsi yang terdapat di finallyblok, sehingga pengaturan selalu dikembalikan.
Dustin Rasener
Saya akan mengedit jawaban untuk anak cucu. Saya harap saya melakukan ini dengan benar! :)
Dustin Rasener
11

@override_settings sangat bagus jika Anda tidak memiliki banyak perbedaan antara konfigurasi lingkungan produksi dan pengujian.

Dalam kasus lain, Anda sebaiknya memiliki file pengaturan yang berbeda. Dalam hal ini proyek Anda akan terlihat seperti ini:

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

Jadi Anda perlu memiliki sebagian besar pengaturan Anda base.pydan kemudian di file lain Anda perlu mengimpor semua dari sana, dan mengganti beberapa opsi. test.pyFile Anda akan terlihat seperti ini:

from .base import *

DEBUG = False

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

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

LOGGING = {}

Dan kemudian Anda perlu menentukan --settingsopsi seperti dalam jawaban @MicroPyramid, atau menentukan DJANGO_SETTINGS_MODULEvariabel lingkungan dan kemudian Anda dapat menjalankan pengujian Anda:

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 
Dmitrii Mikhailov
sumber
Halo. Dmitrii, terima kasih atas jawaban Anda, saya memiliki kasus yang sama dengan jawaban ini, tetapi saya ingin mendapatkan lebih banyak panduan tentang bagaimana aplikasi akan tahu, lingkungan tempat kita berada (pengujian atau produksi) , lihat cabang saya, lihat repo saya github.com/andela/ah-backend-iroquois/tree/develop/authors , seperti bagaimana saya akan menangani logika itu?
Lutaaya Huzaifah Idris
Karena saya menggunakan nosetests untuk menjalankan tes, sekarang bagaimana ini akan dijalankan ?, di lingkungan pengujian bukan di lingkungan pengembangan
Lutaaya Huzaifah Idris
3

Menemukan ini saat mencoba memperbaiki beberapa doctests ... Untuk kelengkapan, saya ingin menyebutkan bahwa jika Anda akan mengubah pengaturan saat menggunakan doctests, Anda harus melakukannya sebelum mengimpor yang lain ...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

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

Untuk pengguna pytest .

Masalah terbesarnya adalah:

  • override_settings tidak bekerja dengan pytest.
  • Subkelas Django TestCaseakan membuatnya bekerja tetapi kemudian anda tidak dapat menggunakan perlengkapan pytest.

Solusinya adalah dengan menggunakan settingsperlengkapan yang didokumentasikan di sini .

Contoh

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

Dan jika Anda perlu memperbarui beberapa bidang

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)
Pithikos
sumber
3

Anda dapat mengganti pengaturan bahkan untuk satu fungsi pengujian.

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

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

atau Anda dapat mengganti pengaturan untuk setiap fungsi di kelas.

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

    def test_some_function():
        
shivansh
sumber
1

Saya menggunakan pytest.

Saya berhasil menyelesaikan ini dengan cara berikut:

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
Brontes
sumber
1

Anda dapat mengganti pengaturan dalam pengujian dengan cara ini:

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

Dan jika Anda membutuhkan pengaturan yang sama di file lain, Anda dapat langsung mengimpor test_settings.

giantas
sumber
0

Jika Anda memiliki beberapa file uji yang ditempatkan di subdirektori (paket python), Anda dapat mengganti pengaturan untuk semua file ini berdasarkan kondisi keberadaan string 'uji' di 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)

Bukan pendekatan terbaik. Digunakan untuk mengubah broker Celery dari Redis ke Memory.

Ledorub
sumber
0

Saya membuat file settings_test.py baru yang akan mengimpor semuanya dari file settings.py dan memodifikasi apa pun yang berbeda untuk tujuan pengujian. Dalam kasus saya, saya ingin menggunakan bucket penyimpanan cloud yang berbeda saat menguji. masukkan deskripsi gambar di sini

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)
Aseem
sumber