यूनिट परीक्षण विधियों के लिए सर्वोत्तम अभ्यास जो कैश का भारी उपयोग करते हैं?


17

मेरे पास कई व्यावसायिक तर्क विधियां हैं जो कैश से ऑब्जेक्ट्स की सूची और फ़िल्टरिंग (स्टोरिंग) के साथ स्टोर और पुनः प्राप्त करते हैं।

विचार करें

IList<TObject> AllFromCache() { ... }

TObject FetchById(guid id) { ... }

IList<TObject> FilterByPropertry(int property) { ... }

Fetch..और Filter..कॉल करेगा AllFromCacheजो कैश को पॉप्युलेट करेगा और अगर यह नहीं है तो वापस आ जाएगा और अगर यह है तो इसे वापस कर दें।

मैं आमतौर पर यूनिट परीक्षण से दूर भागते हैं। इस प्रकार की संरचना के खिलाफ यूनिट परीक्षण के लिए सर्वोत्तम अभ्यास क्या हैं?

मैंने TestInitialize पर कैश को पॉप्युलेट करने और TestCleanup पर हटाने पर विचार किया, लेकिन यह मेरे लिए सही नहीं लगता, (यह अच्छी तरह से हो सकता है)।

जवाबों:


18

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

बेशक कैश को खुद भी यूनिट टेस्टिंग की जरूरत होती है, जिसके लिए आपको कुछ भी मॉकडाउन करना पड़ता है जो कि इस पर निर्भर करता है।

आप जो वर्णन करते हैं, वह वास्तविक कैश ऑब्जेक्ट का उपयोग करता है, लेकिन एक ज्ञात स्थिति में इसे शुरू करना और परीक्षण के बाद सफाई करना, एक एकीकरण परीक्षण की तरह अधिक है, क्योंकि आप कॉन्सर्ट में कई इकाइयों का परीक्षण कर रहे हैं।


+1 यह सबसे अच्छा तरीका है। इकाई परीक्षण तर्क की जांच करने के लिए एकीकरण परीक्षण वास्तव में आपके द्वारा अपेक्षित रूप से कैश कार्यों को सत्यापित करने के लिए।
टॉम स्क्वायर्स

10

एकल जिम्मेदारी सिद्धांत अपने सबसे अच्छे दोस्त यहाँ है।

सबसे पहले, AllFromCache () को एक रिपॉजिटरी क्लास में स्थानांतरित करें और इसे GetAll () कहें। यह कैश से पुनर्प्राप्त करता है रिपॉजिटरी का कार्यान्वयन विवरण है और इसे कॉलिंग कोड द्वारा नहीं जाना जाना चाहिए।

यह आपके फ़िल्टरिंग वर्ग का परीक्षण अच्छा और आसान बनाता है। यह अब परवाह नहीं करता है कि आप इसे कहाँ से प्राप्त कर रहे हैं।

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

AOP इसके लिए एक अच्छी तकनीक है। यह उन कुछ चीजों में से एक है, जो बहुत अच्छी है।

PostSharp जैसे उपकरणों का उपयोग करके , आप इसे सेट कर सकते हैं ताकि किसी चुने हुए विशेषता के साथ चिह्नित किसी भी विधि को कैश किया जा सके। हालाँकि, यदि यह केवल एक चीज है जिसे आप कैशिंग कर रहे हैं, तो आपको AOP फ्रेमवर्क होने की आवश्यकता नहीं है। बस एक रिपॉजिटरी और एक कैशिंग आवरण है जो एक ही इंटरफ़ेस का उपयोग करता है और कॉलिंग क्लास में इंजेक्ट करता है।

जैसे।

public class ProductManager
{
    private IProductRepository ProductRepository { get; set; }

    public ProductManager
    {
        ProductRepository = productRepository;
    }

    Product FetchById(guid id) { ... }

    IList<Product> FilterByPropertry(int property) { ... }
}

public interface IProductRepository
{
    IList<Product> GetAll();
}

public class SqlProductRepository : IProductRepository
{
    public IList<Product> GetAll()
    {
        // DB Connection, fetch
    }
}

public class CachedProductRepository : IProductRepository
{
    private IProductRepository ProductRepository { get; set; }

    public CachedProductRepository (IProductRepository productRepository)
    {
        ProductRepository = productRepository;
    }

    public IList<Product> GetAll()
    {
        // Check cache, if exists then return, 
        // if not then call GetAll() on inner repository
    }
}

देखें कि आपने ProductManager से रिपॉजिटरी कार्यान्वयन ज्ञान कैसे निकाला है? यह भी देखें कि आपने किस प्रकार एकल जवाबदेही सिद्धांत का पालन किया है जिसमें डेटा निष्कर्षण, डेटा पुनर्प्राप्ति को संभालने वाला वर्ग और कैशिंग को संभालने वाला वर्ग है?

अब आप उन रिपॉजिटरी में से किसी के साथ ProductManager को इंस्टेंट कर सकते हैं और कैशिंग प्राप्त कर सकते हैं ... या नहीं। यह बाद में अविश्वसनीय रूप से उपयोगी है जब आपको एक भ्रमित बग मिलता है कि आपको संदेह है कि कैश का परिणाम है।

productManager = new ProductManager(
                         new SqlProductRepository()
                         );

productManager = new ProductManager(
                         new CachedProductRepository(new SqlProductRepository())
                         );

(यदि आप IOC कंटेनर का उपयोग कर रहे हैं, तो और भी बेहतर। यह स्पष्ट होना चाहिए कि कैसे अनुकूलित किया जाए।)

और, आपके ProductManager परीक्षणों में

IProductRepository repo = MockRepository.GenerateStrictMock<IProductRepository>();

कैश का परीक्षण करने की कोई आवश्यकता नहीं है।

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

और, मैंने ऊपर जिन परिवर्तनों का सुझाव दिया है, वास्तव में वहाँ परीक्षण करने के लिए उतना तर्क नहीं है। वास्तव में महत्वपूर्ण परीक्षण, फ़िल्टरिंग विधि, गेटएल () के विवरण से पूरी तरह से अलग हो जाएगी। GetAll () बस ... सब मिलता है। कहीं से।


यदि आप ProductManager में CachedProductRepository का उपयोग कर रहे हैं, लेकिन SQLProductRepository में मौजूद विधियों का उपयोग करना चाहते हैं, तो आप क्या करते हैं?
जोनाथन

@ जोनाथन: "बस एक रिपॉजिटरी और एक कैशिंग आवरण है जो समान इंटरफ़ेस का उपयोग करते हैं" - यदि उनके पास एक ही इंटरफ़ेस है, तो आप समान विधियों का उपयोग कर सकते हैं। कार्यान्वयन के बारे में कॉलिंग कोड को कुछ भी जानने की आवश्यकता नहीं है।
पीडीआर

3

आपका सुझाव दिया दृष्टिकोण है कि मैं क्या करूँगा। आपके विवरण को देखते हुए, विधि का परिणाम वही होना चाहिए जो ऑब्जेक्ट कैश में मौजूद है या नहीं: आपको अभी भी एक ही परिणाम प्राप्त करना चाहिए। प्रत्येक परीक्षण से पहले एक विशेष तरीके से कैश सेट करके परीक्षण करना आसान है। संभवतः कुछ अतिरिक्त मामले हैं जैसे कि गाइड है nullया किसी वस्तु के पास अनुरोधित संपत्ति नहीं है; जिनका परीक्षण भी किया जा सकता है।

इसके अतिरिक्त, आप इस अपेक्षा पर विचार कर सकते हैं कि वस्तु कैश में मौजूद हो, आपके वापस आने के बाद, चाहे वह पहले स्थान पर कैश में हो। यह विवादास्पद है, जैसा कि कुछ लोग (स्वयं शामिल हैं) तर्क देते हैं कि आप इस बात की परवाह करते हैं कि आप अपने इंटरफ़ेस से क्या प्राप्त करते हैं, न कि आप इसे कैसे प्राप्त करते हैं (यानी आपका परीक्षण जो इंटरफ़ेस अपेक्षित रूप से काम करता है, न कि यह एक विशिष्ट कार्यान्वयन है)। क्या आपको इसे महत्वपूर्ण मानना ​​चाहिए, आपके पास यह परीक्षण करने का अवसर है।


1

मैंने TestInitialize पर कैश को पॉप्युलेट करने और TestCleanup पर हटाने पर विचार किया लेकिन यह मेरे लिए सही नहीं लगता

दरअसल, ऐसा करने का एकमात्र सही तरीका है। यह है कि उन दो कार्यों के लिए कर रहे हैं: पूर्व शर्त सेट, और साफ करने के लिए। यदि पूर्व शर्त पूरी नहीं की जाती है, तो आपका कार्यक्रम काम नहीं कर सकता है।


0

मैं कुछ परीक्षणों पर काम कर रहा था जो हाल ही में कैशिंग का उपयोग करते हैं। मैंने क्लास के चारों ओर एक रैपर बनाया जो कैश के साथ काम करता है, और फिर यह दावा किया था कि इस रैपर को बुलाया जा रहा है।

मैंने यह मुख्य रूप से किया क्योंकि कैश के साथ काम करने वाला मौजूदा वर्ग स्थिर था।


0

ऐसा लगता है कि आप कैशिंग लॉजिक का परीक्षण करना चाहते हैं, लेकिन पॉपुलेशन लॉजिक का नहीं। तो मैं आपको सुझाव दूंगा कि आप उस चीज़ का मज़ाक उड़ाएं जिसे आपको परीक्षण करने की आवश्यकता नहीं है - आबादी।

आपकी AllFromCache()विधि कैश को पॉप्युलेट करने का ध्यान रखती है, और इसे मूल्यों के आपूर्तिकर्ता की तरह कुछ और के लिए भेजा जाना चाहिए। तो आपका कोड दिखेगा

private Supplier<TObject> supplier;

IList<TObject> AllFromCache() {
    if (!cacheInitialized) {
        //whatever logic needed to fill the cache
        cache.putAll(supplier.getValues());
        cacheInitialized = true;
    }

    return  cache.getAll();
}

अब आप परीक्षण के लिए आपूर्तिकर्ता को नकली कर सकते हैं, कुछ पूर्वनिर्धारित मूल्यों को वापस करने के लिए। इस तरह, आप अपने वास्तविक फ़िल्टरिंग और लाने का परीक्षण कर सकते हैं, न कि वस्तुओं को लोड करने का।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.