2009-06-25 15 views
23

मान लीजिए मैं निम्नलिखित Event मॉडल:Django इकाई परीक्षण

from django.db import models 
import datetime 

class Event(models.Model): 
    date_start = models.DateField() 
    date_end = models.DateField() 

    def is_over(self): 
     return datetime.date.today() > self.date_end 

मैं (आज +1 या कुछ और) एक घटना है कि भविष्य में समाप्त होता है बनाने के द्वारा Event.is_over() परीक्षण करना चाहते हैं, और तारीख और समय को दबाकर सिस्टम सोचता है कि हम उस भविष्य की तारीख तक पहुंच गए हैं।

मैं पाइथन से संबंधित सभी सिस्टम टाइम ऑब्जेक्ट्स को स्टब करने में सक्षम होना चाहता हूं। इसमें datetime.date.today(), datetime.datetime.now(), और कोई अन्य मानक दिनांक/समय वस्तुएं शामिल हैं।

ऐसा करने का मानक तरीका क्या है?

उत्तर

29

संपादित: https://pypi.python.org/pypi/freezegun: चूंकि मेरा उत्तर स्वीकार किए जाते हैं जवाब यहाँ मैं इसे अद्यतन करने कर रहा हूँ सबको पता एक बेहतर तरीका इस बीच, freezegun पुस्तकालय में बनाया गया है यह बताने के लिए है। जब मैं परीक्षण में समय को प्रभावित करना चाहता हूं तो मैं अपनी सभी परियोजनाओं में इसका उपयोग करता हूं। इस पर एक नज़र डालो।

मूल जवाब:

इस तरह आंतरिक सामान की जगह यह बुरा दुष्प्रभाव हो सकते हैं क्योंकि हमेशा खतरनाक है। तो आप वास्तव में क्या चाहते हैं, बंदर पैचिंग जितना संभव हो उतना स्थानीय हो।

हम माइकल Foord उत्तम नकली पुस्तकालय का उपयोग करें: http://www.voidspace.org.uk/python/mock/ एक @patch डेकोरेटर जो कुछ कार्यक्षमता पैच है, लेकिन बंदर पैच केवल परीक्षण समारोह के दायरे में रहता है, और समारोह इसके दायरे से बाहर चलाता है के बाद सब कुछ स्वचालित रूप से पुनर्स्थापित किया जाता है ।

एकमात्र समस्या यह है कि आंतरिक datetime मॉड्यूल सी में लागू किया गया है, इसलिए डिफ़ॉल्ट रूप से आप इसे बंदर बंदर करने में सक्षम नहीं होंगे। हमने अपना खुद का सरल कार्यान्वयन करके इसे ठीक किया है जो मजाक कर सकता है।

कुल समाधान इस तरह कुछ है (उदाहरण एक डीजेगो प्रोजेक्ट के भीतर उपयोग किया जाने वाला एक सत्यापनकर्ता फ़ंक्शन है जो यह सत्यापित करने के लिए कि भविष्य में एक तिथि है)। आपको लगता है कि मैंने इसे एक प्रोजेक्ट से लिया है, लेकिन गैर-महत्वपूर्ण चीजें ली हैं, इसलिए चीजें वास्तव में काम नहीं कर सकती हैं, लेकिन आपको यह विचार मिल रहा है, लेकिन मुझे आशा है, मुझे उम्मीद है :)

सबसे पहले हम अपने स्वयं के बहुत सरल को परिभाषित करते हैं

import datetime 

def today(): 
    return datetime.date.today() 

फिर हम tests.py में इस सत्यापनकर्ता के लिए unittest बनाने: एक फ़ाइल में datetime.date.today के कार्यान्वयन utils/date.py बुलाया

import datetime 
import mock 
from unittest2 import TestCase 

from django.core.exceptions import ValidationError 

from .. import validators 

class ValidationTests(TestCase): 
    @mock.patch('utils.date.today') 
    def test_validate_future_date(self, today_mock): 
     # Pin python's today to returning the same date 
     # always so we can actually keep on unit testing in the future :) 
     today_mock.return_value = datetime.date(2010, 1, 1) 

     # A future date should work 
     validators.validate_future_date(datetime.date(2010, 1, 2)) 

     # The mocked today's date should fail 
     with self.assertRaises(ValidationError) as e: 
      validators.validate_future_date(datetime.date(2010, 1, 1)) 
     self.assertEquals([u'Date should be in the future.'], e.exception.messages) 

     # Date in the past should also fail 
     with self.assertRaises(ValidationError) as e: 
      validators.validate_future_date(datetime.date(2009, 12, 31)) 
     self.assertEquals([u'Date should be in the future.'], e.exception.messages) 

अंतिम कार्यान्वयन इस तरह दिखता है:

012,
from django.utils.translation import ugettext_lazy as _ 
from django.core.exceptions import ValidationError 

from utils import date 

def validate_future_date(value): 
    if value <= date.today(): 
     raise ValidationError(_('Date should be in the future.')) 

आशा इस

+0

यह वास्तव में सही तरीका है। यह नकली लाइब्रेरी शानदार है। – hendrixski

+1

मैंने एक वैकल्पिक विधि ब्लॉग किया डेटाटाइम के साथ नकली/पैच का उपयोग करने के लिए: http: //www.voidspace.org.uk/python/weblog/arch_d7_2010_10_02.shtml#e1188 – fuzzyman

-5

दो विकल्प।

  1. अपना खुद का प्रदान करके डेटाटाइम आउट करें। चूंकि स्थानीय निर्देशिका को मानक लाइब्रेरी निर्देशिका से पहले खोजा जाता है, इसलिए आप अपने परीक्षण को डेटाटाइम के अपने स्वयं के नकली संस्करण के साथ निर्देशिका में डाल सकते हैं। यह दिखाई देने से कठिन है, क्योंकि आप सभी जगहों को नहीं जानते हैं, समय-समय पर गुप्त रूप से उपयोग किया जाता है।

  2. रणनीति का उपयोग करें। फैक्टरी के साथ आपके कोड में datetime.date.today() और datetime.date.now() पर स्पष्ट संदर्भों को बदलें। फैक्टरी को मॉड्यूल (या unittest) द्वारा मॉड्यूल के साथ कॉन्फ़िगर किया जाना चाहिए। इस कॉन्फ़िगरेशन (जिसे कुछ द्वारा "निर्भरता इंजेक्शन" कहा जाता है) आपको एक विशेष परीक्षण कारखाने के साथ सामान्य रन-टाइम फैक्टरी को प्रतिस्थापित करने की अनुमति देता है। उत्पादन के किसी विशेष मामले को संभालने के साथ आपको बहुत लचीलापन मिलता है। नहीं "अगर परीक्षण अलग-अलग करता है" व्यवसाय।

यहाँ रणनीति संस्करण है।

class DateTimeFactory(object): 
    """Today and now, based on server's defined locale. 

    A subclass may apply different rules for determining "today". 
    For example, the broswer's time-zone could be used instead of the 
    server's timezone. 
    """ 
    def getToday(self): 
     return datetime.date.today() 
    def getNow(self): 
     return datetime.datetime.now() 

class Event(models.Model): 
    dateFactory= DateTimeFactory() # Definitions of "now" and "today". 
    ... etc. ... 

    def is_over(self): 
     return dateFactory.getToday() > self.date_end 


class DateTimeMock(object): 
    def __init__(self, year, month, day, hour=0, minute=0, second=0, date=None): 
     if date: 
      self.today= date 
      self.now= datetime.datetime.combine(date,datetime.time(hour,minute,second)) 
     else: 
      self.today= datetime.date(year, month, day) 
      self.now= datetime.datetime(year, month, day, hour, minute, second) 
    def getToday(self): 
     return self.today 
    def getNow(self): 
     return self.now 

अब आप लंबे समय में इस

class SomeTest(unittest.TestCase): 
    def setUp(self): 
     tomorrow = datetime.date.today() + datetime.timedelta(1) 
     self.dateFactoryTomorrow= DateTimeMock(date=tomorrow) 
     yesterday = datetime.date.today() + datetime.timedelta(1) 
     self.dateFactoryYesterday= DateTimeMock(date=yesterday) 
    def testThis(self): 
     x= Event(...) 
     x.dateFactory= self.dateFactoryTomorrow 
     self.assertFalse(x.is_over()) 
     x.dateFactory= self.dateFactoryYesterday 
     self.asserTrue(x.is_over()) 

कर सकते हैं, आप और अधिक या कम इस करना चाहिए ब्राउज़र स्थान सर्वर स्थान से अलग के लिए खाते। डिफ़ॉल्ट datetime.datetime.now() का उपयोग सर्वर के लोकेल का उपयोग करता है, जो एक अलग समय क्षेत्र में उपयोगकर्ताओं को बंद कर सकता है।

+0

मुझे विशेष रूप से इस समाधान को पसंद नहीं है क्योंकि इसमें गैर-मानक दिनांक/समय विधियों का उपयोग करके परीक्षण कोड के लिए उत्पादन कोड जटिल करना शामिल है। – Fragsworth

+0

(ए) वे मानक datetime.datetime.now() फ़ंक्शन कॉल हैं। गैर मानक क्या है? (बी) सभी डिज़ाइनों को रणनीति (या (बी) निर्भरता इंजेक्शन) की अनुमति देनी चाहिए क्योंकि इस तरह यूनिटटेस्टिंग (और आर्किटेक्चर) अच्छी तरह से किया जाता है। –

+0

यह आपके द्वारा ग्राउंड अप से निर्मित सिस्टम के लिए काम कर सकता है, लेकिन कई तृतीय पक्ष पुस्तकालयों को एक साथ खींचते समय (प्रत्येक मूल datetime.datetime.now() को कॉल करता है) यह रखरखाव समस्या बन सकता है। मैं संशोधित करने के लिए तीसरे पक्ष पुस्तकालय कोड की मात्रा को कम करना चाहता हूं, इसलिए मूल पायथन विधियों के परिणामों को बदलने वाला समाधान आदर्श होगा। – Fragsworth

7

आप अपना खुद का डेटाटाइम मॉड्यूल प्रतिस्थापन कक्षा लिख ​​सकते हैं, जो डेटाटाइम से विधियों और कक्षाओं को लागू कर सकते हैं जिन्हें आप प्रतिस्थापित करना चाहते हैं। उदाहरण के लिए:

import datetime as datetime_orig 

class DatetimeStub(object): 
    """A datetimestub object to replace methods and classes from 
    the datetime module. 

    Usage: 
     import sys 
     sys.modules['datetime'] = DatetimeStub() 
    """ 
    class datetime(datetime_orig.datetime): 

     @classmethod 
     def now(cls): 
      """Override the datetime.now() method to return a 
      datetime one year in the future 
      """ 
      result = datetime_orig.datetime.now() 
      return result.replace(year=result.year + 1) 

    def __getattr__(self, attr): 
     """Get the default implementation for the classes and methods 
     from datetime that are not replaced 
     """ 
     return getattr(datetime_orig, attr) 

के लिए अपने स्वयं के मॉड्यूल में इस रखते हैं हम अपने परीक्षण के शुरू में फिर datetimestub.py

फोन करता हूँ, आप इस कर सकते हैं:

import sys 
import datetimestub 

sys.modules['datetime'] = datetimestub.DatetimeStub() 

किसी भी बाद में आयात datetime मॉड्यूल का उपयोग datetimestub.DatetimeStub उदाहरण का उपयोग करेगा, क्योंकि जब sys.modules शब्दकोश में एक मॉड्यूल का नाम कुंजी के रूप में उपयोग किया जाता है, तो मॉड्यूल आयात नहीं किया जाएगा: sys.modules[module_name] पर ऑब्जेक्ट का उपयोग किया जाएगा।

6

स्टीफ के समाधान में थोड़ा बदलाव। बल्कि विश्व स्तर पर बजाय datetime की जगह की तुलना में आप सिर्फ बस मॉड्यूल परीक्षण किए जाने, उदा .:

 

import models # your module with the Event model 
import datetimestub 

models.datetime = datetimestub.DatetimeStub() 
 

इस तरह परिवर्तन भी बहुत कुछ अपने परीक्षण के दौरान स्थानीय है में datetime मॉड्यूल बदल सकते थे।

+0

या, शायद 'टाइमटाइम के रूप में mockdatetime आयात करें'? –

+2

उस कोड को बदलने में शामिल होगा जिसमें आप परीक्षण कर रहे थे, है ना? आप वास्तव में मॉडल मॉड्यूल में "डेटाटाइम" नाम को फिर से बांधना चाहते हैं। –

+0

दिन के अंत में यह आपके कोड को अनावश्यक रूप से जटिल करने से बचने के लिए पाइथन की गतिशील प्रकृति का लाभ उठाने के बारे में है। –

1

यह सिस्टम-व्यापी डेटाटाइम प्रतिस्थापन नहीं करता है, लेकिन यदि आप कुछ काम करने की कोशिश करने के साथ तंग आते हैं तो आप परीक्षण के लिए इसे आसान बनाने के लिए हमेशा एक वैकल्पिक पैरामीटर जोड़ सकते हैं।

def is_over(self, today=datetime.datetime.now()): 
    return today > self.date_end 
+0

मुझे यकीन नहीं है कि यह एक लंबे समय से चलने वाले धागे पर काम करेगा जैसे कि आप django + mod_wsgi वातावरण में क्या करेंगे। मुझे लगता है कि डिफ़ॉल्ट रूप से आपके प्रोग्राम को लोड होने पर पहली बार संकलित किया जाएगा और फिर अगली बार कोड को फिर से लोड किए जाने तक ही वही रहेगा। – Aaron

+0

धन्यवाद, मैंने इसे इसके लिए खाते में अपडेट करने का प्रयास किया है। – monkut

+1

आप इसे और कम कर सकते हैं: def is_over (self, today = datetime.datetime.now(): आज वापस लौटें> self.date_end –

3

क्या होगा यदि आप self.end_date बजाय datetime मज़ाक उड़ाया मदद करता है? फिर आप अभी भी परीक्षण कर सकते हैं कि फ़ंक्शन वह कर रहा है जो आप चाहते हैं कि सभी अन्य पागल कामकाजों के बिना।

यह आपको आपके प्रश्न की तरह सभी दिनांक/समय को शुरू करने की अनुमति नहीं देगा, लेकिन यह पूरी तरह से आवश्यक नहीं हो सकता है।

 
today = datetime.date.today() 

event1 = Event() 
event1.end_date = today - datetime.timedelta(days=1) # 1 day ago 
event2 = Event() 
event2.end_date = today + datetime.timedelta(days=1) # 1 day in future 

self.assertTrue(event1.is_over()) 
self.assertFalse(event2.is_over()) 
संबंधित मुद्दे