परीक्षण के दौरान DateTime.Now को अधिलेखित करने का एक अच्छा तरीका क्या है?


116

मुझे कुछ (C #) कोड मिले हैं जो भविष्य में चीजों की सही गणना करने के लिए आज की तारीख पर निर्भर करते हैं। यदि मैं परीक्षण में आज की तारीख का उपयोग करता हूं, तो मुझे परीक्षण में गणना को दोहराना होगा, जो सही नहीं लगता है। परीक्षण के भीतर एक ज्ञात मूल्य पर तारीख निर्धारित करने का सबसे अच्छा तरीका क्या है ताकि मैं यह परीक्षण कर सकूं कि परिणाम एक ज्ञात मूल्य है?

जवाबों:


157

मेरी प्राथमिकता उन वर्गों की है जो समय का उपयोग करते हैं, वास्तव में एक इंटरफ़ेस पर निर्भर होते हैं, जैसे कि

interface IClock
{
    DateTime Now { get; } 
}

एक ठोस कार्यान्वयन के साथ

class SystemClock: IClock
{
     DateTime Now { get { return DateTime.Now; } }
}

फिर यदि आप चाहें, तो आप किसी भी अन्य प्रकार की घड़ी प्रदान कर सकते हैं जिसे आप परीक्षण के लिए चाहते हैं, जैसे

class StaticClock: IClock
{
     DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } }
}

उस वर्ग को घड़ी प्रदान करने में कुछ ओवरहेड हो सकता है, जो उस पर निर्भर करता है, लेकिन उस पर निर्भरता इंजेक्शन समाधानों के किसी भी संख्या को नियंत्रित किया जा सकता है (नियंत्रण कंटेनर के व्युत्क्रम का उपयोग करके, सादे पुराने निर्माता / सेटर इंजेक्शन, या यहां तक ​​कि एक स्टेटिक मार्ग पैटर्न )।

किसी ऑब्जेक्ट या विधि को वितरित करने के अन्य तंत्र जो वांछित समय प्रदान करते हैं, वे भी काम करते हैं, लेकिन मुझे लगता है कि सिस्टम क्लॉक को रीसेट करने से बचना महत्वपूर्ण है, क्योंकि यह अन्य स्तरों पर दर्द का परिचय देने वाला है।

इसके अलावा, इसका उपयोग करना DateTime.Nowऔर इसे अपनी गणना में शामिल करना सिर्फ सही नहीं लगता है - यह आपको विशेष समय का परीक्षण करने की क्षमता से लूटता है, उदाहरण के लिए यदि आप एक बग की खोज करते हैं जो केवल आधी रात की सीमा के पास होता है, या मंगलवार को होता है। वर्तमान समय का उपयोग करने से आप उन परिदृश्यों का परीक्षण नहीं कर पाएंगे। या कम से कम जब भी आप चाहें।


2
हमने वास्तव में xUnit.net एक्सटेंशन में से एक में इसे औपचारिक रूप दिया था। हमारे पास एक क्लॉक क्लास है जिसे आप डेटाइम के बजाय एक स्थिर के रूप में उपयोग करते हैं, और आप विशिष्ट तिथियों सहित, घड़ी को "फ्रीज" और "पिघलना" कर सकते हैं। देखिए .gd/3xds और is.gd/3xdu
ब्रैड विल्सन

2
यह भी ध्यान देने योग्य है कि जब आप अपनी सिस्टम क्लॉक विधि को स्थानापन्न करना चाहते हैं - यह होगा, उदाहरण के लिए, व्यापक रूप से बिखरे हुए समय क्षेत्रों में शाखाओं के साथ एक उद्यम में एक वैश्विक घड़ी का उपयोग करते समय - यह विधि आपको मूल्यवान व्यापार-स्तर की स्वतंत्रता को बदलने की छूट देती है। "अब" का अर्थ।
माइक बर्टन

1
यह तरीका मेरे लिए वास्तव में अच्छी तरह से काम करता है, साथ ही IClock उदाहरण के लिए एक डिपेंडेंसी इंजेक्शन फ्रेमवर्क का उपयोग करता है।
विल्का

8
बहुत बढ़िया जवाब। बस यह जोड़ना चाहता था कि लगभग सभी मामलों में UtcNowउपयोग किया जाना चाहिए और फिर कोड की चिंता के अनुसार उचित रूप से समायोजित किया जाना चाहिए, जैसे कि व्यापार तर्क, यूआई, आदि। टाइमज़ोन में डेटटाइम हेरफेर एक खान क्षेत्र है, लेकिन सबसे अच्छा पहला पैर आगे हमेशा के साथ शुरू करना है UTC समय।
एडम राल्फ

@ ब्रैडविलसन वे लिंक अब टूट गए हैं। वेबैक पर उन्हें नहीं खींच सकते, या तो।

55

आयेंदे रहीन एक स्थिर विधि का उपयोग करता है जो सरल है ...

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}

1
स्टब / मॉक पॉइंट को सार्वजनिक वैश्विक चर (क्लास स्टैटिक वेरिएबल) बनाना खतरनाक लगता है। क्या इसे केवल परीक्षण के तहत प्रणाली में सीमित करना बेहतर नहीं होगा - उदाहरण के लिए, यह परीक्षण के तहत कक्षा का एक निजी स्थिर सदस्य बनाता है?
हारून

1
यह स्टाइल की बात है। इकाई-परीक्षण-परिवर्तनशील प्रणाली समय प्राप्त करने के लिए आप कम से कम काम कर सकते हैं।
एंथनी मस्तरीन

3
IMHO, यह वैश्विक स्थिर एकल के बजाय इंटरफ़ेस का उपयोग करना पसंद करता है। इस परिदृश्य पर विचार करें: परीक्षण धावक कुशल है और वह जितने भी परीक्षण कर रहा है, उसके समानांतर चल रहा है, एक परीक्षण एक्स को दिए गए समय में परिवर्तन करता है, दूसरा वाई के लिए। हमारे पास अब एक संघर्ष है और ये दोनों परीक्षण विफलताओं को टॉगल करेंगे। यदि हम इंटरफेस का उपयोग करते हैं, तो प्रत्येक परीक्षण उसकी आवश्यकताओं के अनुसार इंटरफ़ेस को मॉक करता है, प्रत्येक परीक्षण अब अन्य परीक्षण से अलग हो गया है। HTH।
श्लोइमी

17

मुझे लगता है कि कुछ सरल के लिए एक अलग क्लॉक क्लास बनाना जैसे कि वर्तमान तिथि थोड़ी अधिक है।

आप आज की तारीख को एक पैरामीटर के रूप में पास कर सकते हैं ताकि आप परीक्षण में एक अलग तारीख को इनपुट कर सकें। इससे आपके कोड को अधिक लचीला बनाने का अतिरिक्त लाभ होता है।


मैं तुम्हारा और ब्लेयर दोनों के जवाबों पर 1 +1 करूंगा, भले ही वे दोनों विरोध कर रहे हों। मुझे लगता है कि दोनों दृष्टिकोण मान्य हैं। आपका दृष्टिकोण मैं शायद एक ऐसी परियोजना का उपयोग करूंगा जो एकता जैसी चीज का उपयोग नहीं करती है।
रिचर्ड नोड 27'09

1
हां, "अब" के लिए एक पैरामीटर जोड़ना संभव है। हालाँकि, कुछ मामलों में यह आपको एक ऐसे पैरामीटर को उजागर करने की आवश्यकता होती है जिसे आप सामान्य रूप से उजागर नहीं करना चाहते हैं। उदाहरण के लिए, मान लें कि आपके पास एक ऐसा तरीका है जो किसी तिथि और अब के बीच के दिनों की गणना करता है, तो आप "अब" को एक पैरामीटर के रूप में उजागर नहीं करना चाहते हैं क्योंकि यह आपके परिणाम में हेरफेर की अनुमति देता है। यदि यह आपका अपना कोड है, तो पैरामीटर के रूप में "अभी" जोड़ें आगे बढ़ें, लेकिन यदि आप एक टीम में काम कर रहे हैं, तो आप कभी नहीं जानते कि अन्य डेवलपर आपके कोड का उपयोग किस लिए करेंगे, इसलिए यदि "अब" आपके कोड का एक महत्वपूर्ण हिस्सा है, तो आपको इसकी आवश्यकता होगी इसे जोड़-तोड़ या दुरुपयोग से बचाएं।
Stitch10925

17

एक शिम बनाने के लिए माइक्रोसॉफ्ट फेक का उपयोग करना ऐसा करने का एक बहुत आसान तरीका है। मान लीजिए कि मेरे पास निम्न वर्ग है:

public class MyClass
{
    public string WhatsTheTime()
    {
        return DateTime.Now.ToString();
    }

}

विज़ुअल स्टूडियो 2012 में, आप अपनी विधानसभा में सही तरीके से अपनी परीक्षण परियोजना के लिए एक फेक असेंबली जोड़ सकते हैं, जिसके लिए आप फेक / शम्स बनाना चाहते हैं और "ऐड फ़ेक असेंबली" का चयन कर सकते हैं

फ़ेक असेंबली जोड़ना

अंत में, यहाँ परीक्षा वर्ग कैसा दिखेगा:

using System;
using ConsoleApplication11;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DateTimeTest
{
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestWhatsTheTime()
    {

        using(ShimsContext.Create()){

            //Arrange
            System.Fakes.ShimDateTime.NowGet =
            () =>
            { return new DateTime(2010, 1, 1); };

            var myClass = new MyClass();

            //Act
            var timeString = myClass.WhatsTheTime();

            //Assert
            Assert.AreEqual("1/1/2010 12:00:00 AM",timeString);

        }
    }
}
}

1
यह ठीक वही है जिसकी मुझे तलाश थी। धन्यवाद! BTW, वीएस 2013 में एक ही काम करता है।
डगलस लुडलो

या इन दिनों, वीएस 2015 एंटरप्राइज संस्करण। जो इस तरह के सबसे अच्छे अभ्यास के लिए शर्म की बात है।
आरजेबी

12

सफल इकाई परीक्षण की कुंजी डिकम्पलिंग है । आपको अपने दिलचस्प कोड को इसके बाहरी निर्भरता से अलग करना होगा, इसलिए इसे अलगाव में परीक्षण किया जा सकता है। (सौभाग्य से, परीक्षण-संचालित विकास डिकोड किए गए कोड का उत्पादन करता है।)

इस स्थिति में, आपका बाहरी वर्तमान DateTime है।

यहाँ मेरी सलाह तर्क को निकालने की है जो डेटाइम से संबंधित एक नई विधि या वर्ग या जो भी आपके मामले में समझ में आता है, और डेटाइम को पास कर देता है। अब, आपके यूनिट टेस्ट में मनमाने ढंग से डेटटाइम पास कर सकते हैं, जो कि अनुमानित परिणाम दे सकते हैं।


10

Microsoft मोल्स ( .NET के लिए अलगाव फ्रेमवर्क ) का उपयोग कर एक और ।

MDateTime.NowGet = () => new DateTime(2000, 1, 1);

मोल्स किसी प्रतिनिधि के साथ किसी भी .NET विधि को बदलने की अनुमति देता है। मोल्स स्थिर या गैर-आभासी तरीकों का समर्थन करता है। मोक्स Pex से प्रोफाइलर पर निर्भर करता है।


यह सुंदर है, लेकिन इसके लिए विजुअल स्टूडियो 2010 की आवश्यकता है! :-(
पंडिंकस

यह वीएस 2008 में भी ठीक काम करता है। हालांकि यह MSTest के साथ सबसे अच्छा काम करता है। आप NUnit का उपयोग कर सकते हैं, लेकिन फिर मुझे लगता है कि आपको एक विशेष परीक्षण धावक के साथ अपने परीक्षण चलाने होंगे।
Torbjørn

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

1
@ ब्रायनपीरिस, माइक्रोसॉफ्ट फेक का उपयोग करने के लिए डाउनसाइड क्या हैं?
रे चेंग

आप हमेशा 3rd पार्टी कंटेंट को डिसाइड नहीं कर सकते, इसलिए फेक यूनिट टेस्ट के लिए पूरी तरह से स्वीकार्य है यदि आपका कोड 3rd पार्टी एपीआई में कॉल करता है जिसे आप यूनिट टेस्ट के लिए तुरंत नहीं चाहते हैं। मैं आपके अपने कोड के लिए फेक / मोल्स का उपयोग करने से बचने के लिए सहमत हूं, लेकिन अन्य उद्देश्यों के लिए इसकी पूरी तरह से स्वीकार्य है।
क्रिसकॉ

5

मेरा सुझाव है कि आइडीएसोपॉली पैटर्न का उपयोग करें:

[Test] 
public void CreateName_AddsCurrentTimeAtEnd() 
{
    using (Clock.NowIs(new DateTime(2010, 12, 31, 23, 59, 00)))
    {
        string name = new ReportNameService().CreateName(...);
        Assert.AreEqual("name 2010-12-31 23:59:00", name);
    } 
}

यहाँ वर्णित विस्तार से: http://www.lesnikowski.com/blog/index.php/testing-datetime-now/


3

सरल उत्तर: ditch System.DateTime :) इसके बजाय, NodaTime का उपयोग करें और यह लाइब्रेरी का परीक्षण कर रहा है: NodaTime.Testing

आगे की पढाई:


2

आप जिस कक्षा में परीक्षण कर रहे हैं, उसके लिए आप कक्षा को बेहतर (बेहतर: विधि / प्रतिनिधि ) का उपयोग कर सकते हैं DateTime.Now। HaveDateTime.Now एक डिफ़ॉल्ट मान और केवल एक डमी विधि है कि रिटर्न एक निरंतर मूल्य के लिए परीक्षण में यह सेट किया जा।

EDIT: ब्लेयर कॉनराड ने क्या कहा (उसके पास देखने के लिए कुछ कोड है)। सिवाय इसके, मैं इसके लिए प्रतिनिधियों को पसंद करता हूं, क्योंकि वे सामान के साथ आपकी कक्षा पदानुक्रम को अव्यवस्थित नहीं करते हैं IClock...


1

मुझे इस स्थिति का इतनी बार सामना करना पड़ा, कि मैंने सरल नगेट बनाया, जो अब प्रॉपर्टी को इंटरफ़ेस के माध्यम से उजागर करता है।

public interface IDateTimeTools
{
    DateTime Now { get; }
}

कार्यान्वयन बेशक बहुत सीधा है

public class DateTimeTools : IDateTimeTools
{
    public DateTime Now => DateTime.Now;
}

इसलिए अपने प्रोजेक्ट में नगेट जोड़ने के बाद मैं इसे यूनिट परीक्षणों में उपयोग कर सकता हूं

यहां छवि विवरण दर्ज करें

आप GUI Nuget पैकेज प्रबंधक से या कमांड का उपयोग करके मॉड्यूल को स्थापित कर सकते हैं:

Install-Package -Id DateTimePT -ProjectName Project

और Nuget के लिए कोड यहाँ है

Autofac के साथ उपयोग का उदाहरण यहां पाया जा सकता है


-11

आपने डिबग / तैनाती के दौरान क्या होता है, इसे नियंत्रित करने के लिए सशर्त संकलन का उपयोग करने पर विचार किया है?

जैसे

DateTime date;
#if DEBUG
  date = new DateTime(2008, 09, 04);
#else
  date = DateTime.Now;
#endif

असफल होने पर, आप संपत्ति को उजागर करना चाहते हैं ताकि आप इसे हेरफेर कर सकें, यह परीक्षण योग्य कोड लिखने की चुनौती का एक हिस्सा है , जो कि मैं वर्तमान में खुद कुश्ती कर रहा हूं: डी।

संपादित करें

मेरा एक बड़ा हिस्सा ब्लेयर के दृष्टिकोण को वरीयता देगा । यह आपको परीक्षण में सहायता के लिए कोड के "गर्म प्लग" भागों की अनुमति देता है। यह सभी डिजाइन सिद्धांत का पालन करता है करता है जो बदलता है कि परीक्षण कोड उत्पादन कोड से अलग नहीं है, इसका सिर्फ कोई भी इसे बाहरी रूप से कभी नहीं देखता है।

इस उदाहरण के लिए बनाना और इंटरफ़ेस बहुत काम की तरह लग सकता है (हालांकि यही कारण है कि मैंने सशर्त संकलन का विकल्प चुना है)।


वाह आप इस जवाब पर कड़ी चोट कर रहे हैं। यह वही है जो मैं कर रहा हूं, हालांकि एक ही स्थान पर, जहां मैं उन तरीकों को बुलाता हूं जो DateTime's Nowऔर Todayआदि की जगह लेते हैं
डेव कजिनो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.