2009-03-03 18 views
9

सी #, एनयूनीट, और राइनो मोक्स, यदि लागू हो जाता है तो यह हो रहा है।टीडीडी और डीआई: निर्भरता इंजेक्शन बोझिल

टीडीडी के साथ मेरी खोज जारी है क्योंकि मैं एक जटिल कार्य के आसपास परीक्षण लपेटने का प्रयास करता हूं। आइए मान लें कि मैं एक फॉर्म को कोडिंग कर रहा हूं, जब सहेजे जाने पर, आश्रित वस्तुओं को फॉर्म के भीतर भी सहेजना होगा ... फ़ॉर्म के सवालों के जवाब, उपलब्ध होने पर अनुलग्नक, और "लॉग" प्रविष्टियां (जैसे कि "ब्लाब्लाह ने फॉर्म को अपडेट किया।" या "blahblah एक फाइल संलग्न।")। सहेजने के दौरान फॉर्म की स्थिति कैसे बदलती है, इस पर निर्भर करता है कि यह सहेजने वाला फ़ंक्शन विभिन्न लोगों को ईमेल भी निकाल देता है।

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

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

+0

यह सुधार का सुझाव दे करने में मदद मिलेगी अगर आप अपने कोड में दिखाई देगा। –

उत्तर

7

एक ऑटोमॉकिंग कंटेनर का उपयोग करें। RhinoMocks के लिए एक लिखा है।

कल्पना कीजिए कि आपके पास कन्स्ट्रक्टर इंजेक्शन के माध्यम से इंजेक्शन वाली कई निर्भरताओं के साथ एक कक्षा है।,

private MockRepository _mocks; 
private BroadcastListViewPresenter _presenter; 
private IBroadcastListView _view; 
private IAddNewBroadcastEventBroker _addNewBroadcastEventBroker; 
private IBroadcastService _broadcastService; 
private IChannelService _channelService; 
private IDeviceService _deviceService; 
private IDialogFactory _dialogFactory; 
private IMessageBoxService _messageBoxService; 
private ITouchScreenService _touchScreenService; 
private IDeviceBroadcastFactory _deviceBroadcastFactory; 
private IFileBroadcastFactory _fileBroadcastFactory; 
private IBroadcastServiceCallback _broadcastServiceCallback; 
private IChannelServiceCallback _channelServiceCallback; 

[SetUp] 
public void SetUp() 
{ 
    _mocks = new MockRepository(); 
    _view = _mocks.DynamicMock<IBroadcastListView>(); 

    _addNewBroadcastEventBroker = _mocks.DynamicMock<IAddNewBroadcastEventBroker>(); 

    _broadcastService = _mocks.DynamicMock<IBroadcastService>(); 
    _channelService = _mocks.DynamicMock<IChannelService>(); 
    _deviceService = _mocks.DynamicMock<IDeviceService>(); 
    _dialogFactory = _mocks.DynamicMock<IDialogFactory>(); 
    _messageBoxService = _mocks.DynamicMock<IMessageBoxService>(); 
    _touchScreenService = _mocks.DynamicMock<ITouchScreenService>(); 
    _deviceBroadcastFactory = _mocks.DynamicMock<IDeviceBroadcastFactory>(); 
    _fileBroadcastFactory = _mocks.DynamicMock<IFileBroadcastFactory>(); 
    _broadcastServiceCallback = _mocks.DynamicMock<IBroadcastServiceCallback>(); 
    _channelServiceCallback = _mocks.DynamicMock<IChannelServiceCallback>(); 


    _presenter = new BroadcastListViewPresenter(
     _addNewBroadcastEventBroker, 
     _broadcastService, 
     _channelService, 
     _deviceService, 
     _dialogFactory, 
     _messageBoxService, 
     _touchScreenService, 
     _deviceBroadcastFactory, 
     _fileBroadcastFactory, 
     _broadcastServiceCallback, 
     _channelServiceCallback); 

    _presenter.View = _view; 
} 

अब यहाँ एक AutoMocking कंटेनर के साथ एक ही बात है:: यहाँ क्या यह सेट अप करने के लिए RhinoMocks, कोई AutoMocking कंटेनर के साथ की तरह लग रहा है

private MockRepository _mocks; 
private AutoMockingContainer _container; 
private BroadcastListViewPresenter _presenter; 
private IBroadcastListView _view; 

[SetUp] 
public void SetUp() 
{ 

    _mocks = new MockRepository(); 
    _container = new AutoMockingContainer(_mocks); 
    _container.Initialize(); 

    _view = _mocks.DynamicMock<IBroadcastListView>(); 
    _presenter = _container.Create<BroadcastListViewPresenter>(); 
    _presenter.View = _view; 

} 

आसान है, हाँ?

AutoMocking कंटेनर स्वचालित रूप से निर्माता में हर निर्भरता के लिए mocks बनाता है, और यदि आप ऐसा तरह के परीक्षण के लिए उन तक पहुँच सकते हैं:

using (_mocks.Record()) 
    { 
     _container.Get<IChannelService>().Expect(cs => cs.ChannelIsBroadcasting(channel)).Return(false); 
     _container.Get<IBroadcastService>().Expect(bs => bs.Start(8)); 
    } 

आशा है कि मदद करता है। मुझे पता है कि ऑटोमॉकिंग कंटेनर के आगमन के साथ मेरा परीक्षण जीवन पूरी तरह से आसान बना दिया गया है।

+0

यह दृष्टिकोण केवल जटिलता को छुपाता है, यह आपको इससे राहत नहीं देता है।यहां मूल समस्या कोड में है जिसका परीक्षण किया जा रहा है, टेस्ट कोड स्वयं नहीं। –

+0

यह करने के लिए एक उचित बात है। बस उन परीक्षणों के लिए देखें जो कई सेवाओं पर अपेक्षाओं को एक साथ स्थापित कर रहे हैं। प्रत्येक परीक्षण को आम तौर पर एक समय में केवल एक ही सेवा के खिलाफ अपेक्षाएं निर्धारित करनी चाहिए। उम्मीद के लिए एक उपकरण रखना अच्छा है-बाकी के लिए कम स्टब्स। –

5

आप सही हैं कि यह बोझिल हो सकता है।

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

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

अंत में, एक मॉकिंग फ्रेमवर्क का उपयोग करें जो आपके लिए कुछ काम करता है।

1

कन्स्ट्रक्टर DI DI करने का एकमात्र तरीका नहीं है। चूंकि आप सी # का उपयोग कर रहे हैं, यदि आपका कन्स्ट्रक्टर कोई महत्वपूर्ण काम नहीं करता है तो आप प्रॉपर्टी डी का उपयोग कर सकते हैं। यह आपके कार्य के जटिलता की कीमत पर आपके ऑब्जेक्ट के रचनाकारों के संदर्भ में चीजों को बहुत सरल बनाता है। आपके फ़ंक्शन को किसी भी आश्रित गुणों की शून्यता की जांच करनी चाहिए और अगर वे काम शुरू करने से पहले शून्य हो तो अमान्य ऑपरेशन फेंकना चाहिए।

+0

असहमत, इसे संपत्ति आधारित बनाने से सरल नहीं होता है, केवल जटिलता को छुपाता है। – eglasius

+0

ठीक है, सरलता से, मेरा मतलब है कि यह आपको जटिलता को एक स्थान से दूसरे स्थान पर स्थानांतरित करने की अनुमति देता है। यह वास्तव में परीक्षण या सिस्टम के अन्य पहलुओं के मामले में चीजों को सरल बना सकता है, जिससे आप छोटे हिस्सों में सरल भागों से निपटने की अनुमति देते हैं। – Randolpho

0

जब किसी चीज का परीक्षण करना मुश्किल होता है, तो यह आमतौर पर कोड की गुणवत्ता का लक्षण होता है, यह कोड टेस्ट करने योग्य नहीं है (this podcast, आईआईआरसी में उल्लिखित)। अनुशंसा कोड को दोबारा करने के लिए है ताकि कोड परीक्षण करना आसान हो। वर्गों में कोड को विभाजित करने का निर्णय लेने के लिए कुछ हेरिस्टिक्स SRP and OCP हैं। अधिक विशिष्ट निर्देशों के लिए, प्रश्न में कोड देखना आवश्यक होगा।

15

सबसे पहले, यदि आप टीडीडी का पालन कर रहे हैं, तो आप एक जटिल कार्य के आसपास परीक्षण लपेटते नहीं हैं। आप अपने परीक्षणों के आस-पास फ़ंक्शन को लपेटते हैं। असल में, यह भी सही नहीं है। आप अपने परीक्षणों और कार्यों में हस्तक्षेप करते हैं, लगभग एक ही समय में दोनों लिखते हैं, परीक्षणों के कुछ ही परीक्षणों के साथ परीक्षण करते हैं। The Three Laws of TDD देखें।

जब आप इन तीन कानूनों का पालन करते हैं, और रिफैक्टरिंग के बारे में मेहनती हैं, तो आप कभी भी "जटिल कार्य" के साथ हवादार नहीं होते हैं। इसके बजाय आप कई, परीक्षण, सरल कार्यों के साथ हवा।

अब, आपके बिंदु पर। यदि आपके पास पहले से ही "जटिल कार्य" है और आप इसके चारों ओर परीक्षण लपेटना चाहते हैं तो आपको:

  1. DI के माध्यम से स्पष्ट रूप से अपने मोजे जोड़ें। (उदाहरण के लिए 'टेस्ट' ध्वज और 'if' कथन की तरह कुछ भयानक है जो वास्तविक वस्तुओं के बजाय मोक्स का चयन करता है)।
  2. घटक के मूल संचालन को कवर करने के लिए कुछ परीक्षण लिखें।
  3. बेकार ढंग से रिफैक्टर, जटिल कार्य को कई छोटे सरल कार्यों में तोड़ते हुए, जितनी बार संभव हो सके अपने कोबल्ड परीक्षणों को चलाते हुए।
  4. जितना संभव हो सके 'टेस्ट' ध्वज को पुश करें। जैसा कि आप रिफैक्टर करते हैं, अपने डेटा स्रोतों को छोटे सरल कार्यों में पास करें। 'परीक्षण' ध्वज को किसी भी शीर्ष कार्य को संक्रमित न होने दें।
  5. रिवाइट परीक्षण। जैसा कि आप रिफैक्टर करते हैं, बड़े शीर्ष-स्तरीय फ़ंक्शन के बजाय सरल छोटे कार्यों को कॉल करने के लिए जितना संभव हो उतना परीक्षण पुनः लिखें। आप अपने परीक्षणों से अपने कार्यों को सरल कार्यों में पारित कर सकते हैं।
  6. 'परीक्षण' ध्वज से छुटकारा पाएं और यह निर्धारित करें कि आपको वास्तव में कितनी डीआई चाहिए। चूंकि आपके पास निचले स्तर पर लिखे गए परीक्षण हैं जो कि स्मारकों के माध्यम से मैक्स डाल सकते हैं, आपको शायद शीर्ष स्तर पर कई डेटा स्रोतों को नकल करने की आवश्यकता नहीं है।

यदि यह सब के बाद, DI अभी भी बोझिल है, तो एक ऐसे ऑब्जेक्ट को इंजेक्शन देने के बारे में सोचें जो आपके सभी डेटा स्रोतों के संदर्भ रखता है। कई लोगों की बजाय एक चीज़ इंजेक्ट करना हमेशा आसान होता है।

+0

@ चाचा बॉब। आपने बताया है कि मैं वास्तव में क्या कर रहा हूं, आंतरिक रेखाओं में। – vijaysylvester

+1

कृपया, कोई भगवान वस्तुओं नहीं। मैंने अपनी ज़िंदगी में बहुत सारी ज़िंदगी बिताई है, जो कि सभी निर्भरता वस्तुओं पर निर्भर करता है जो सभी कोडों को सभी निर्भरताओं पर निर्भर करते हुए मॉड्यूलरिटी को तोड़ देता है। –

+0

@ अंकल बॉब, धन्यवाद। पहले 2 वाक्यों ने मुझे मारा। –

5

मेरे पास आपका कोड नहीं है, लेकिन मेरी पहली प्रतिक्रिया यह है कि आपका परीक्षण आपको यह बताने की कोशिश कर रहा है कि आपके ऑब्जेक्ट में बहुत से सहयोगी हैं। इस तरह के मामलों में, मुझे हमेशा लगता है कि वहां एक लापता निर्माण है जिसे उच्च स्तर की संरचना में पैक किया जाना चाहिए। एक ऑटोमॉकिंग कंटेनर का उपयोग करना सिर्फ आपके परीक्षणों से प्राप्त प्रतिक्रिया को परेशान कर रहा है। लंबी चर्चा के लिए http://www.mockobjects.com/2007/04/test-smell-bloated-constructor.html देखें।

4

इस संदर्भ में, मुझे आम तौर पर "यह इंगित करता है कि आपकी ऑब्जेक्ट में बहुत अधिक निर्भरताएं हैं" या "आपके ऑब्जेक्ट में बहुत से सहयोगी हैं" काफी स्पष्ट दावा होने के लिए मुझे बयानों मिलते हैं। बेशक एक एमवीसी नियंत्रक या एक फॉर्म अपने कर्तव्यों को पूरा करने के लिए कई अलग-अलग सेवाओं और वस्तुओं को बुला रहा है; यह सब के बाद, आवेदन की शीर्ष परत पर बैठे हैं। आप इनमें से कुछ निर्भरताओं को एक साथ उच्च स्तरीय ऑब्जेक्ट्स (कहें, एक शिपिंग मोडर रिपोजिटरी और एक ट्रांजिटटाइम कैलकुलेटर एक शिपिंग रैटफाइंडर में संयुक्त हो सकते हैं) में, लेकिन यह केवल इतना ही है, खासकर इन शीर्ष-स्तर, प्रेजेंटेशन-उन्मुख ऑब्जेक्ट्स के लिए। यह नकली करने के लिए एक कम वस्तु है, लेकिन आपने वास्तव में संकेतों की एक परत के माध्यम से वास्तविक निर्भरताओं को खराब कर दिया है, वास्तव में उन्हें हटाया नहीं है।

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

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

ऑब्जेक्ट मॉडल के लिए यूनिट परीक्षण सहेजें, प्रस्तुति परत के लिए एकीकरण परीक्षण सहेजें, और जब संभव हो तो नकली वस्तुओं से बचें। यदि किसी विशेष निर्भरता का मज़ाक उड़ाना मुश्किल है, तो यह व्यावहारिक होने का समय है: केवल यूनिट परीक्षण न करें और इसके बजाय एकीकरण परीक्षण लिखें और अपना समय बर्बाद करना बंद करें।

3

सरल उत्तर यह है कि आप जिस कोड को परीक्षण करने का प्रयास कर रहे हैं वह बहुत अधिक कर रहा है। मुझे लगता है कि Single Responsibility Principle पर चिपकने से मदद मिल सकती है।

सहेजें बटन विधि में अन्य वस्तुओं पर चीजों को प्रतिनिधि करने के लिए केवल शीर्ष-स्तरीय कॉल होनी चाहिए। इन वस्तुओं को फिर इंटरफेस के माध्यम से सारणित किया जा सकता है। फिर जब आप सहेजें बटन विधि का परीक्षण करते हैं, तो आप केवल मॉक ऑब्जेक्ट्स के साथ बातचीत का परीक्षण करते हैं।

अगला चरण इन निचले स्तर के वर्गों में परीक्षण लिखना है, लेकिन चीज आसान होनी चाहिए क्योंकि आप केवल अलगाव में इनका परीक्षण करते हैं। यदि आपको जटिल परीक्षण सेटअप कोड की आवश्यकता है, तो यह खराब डिजाइन (या खराब परीक्षण दृष्टिकोण) का एक अच्छा संकेतक है।

अनुशंसित पढ़ने:

  1. Clean Code: A Handbook of Agile Software Craftsmanship
  2. Google's guide to writing testable code
संबंधित मुद्दे