2010-07-14 12 views
9

मेरे पास कुछ ट्विस्ट कोड है जो डिफर्रेड की कई श्रृंखलाएं बनाता है। इनमें से कुछ बिना किसी त्रुटि के विफल हो सकते हैं जो उन्हें कॉलबैक श्रृंखला पर वापस रखता है। मैं इस कोड के लिए यूनिट टेस्ट लिखने में सक्षम नहीं हूं - असफल डिफरर्ड परीक्षण कोड पूरा होने के बाद परीक्षण विफल होने का कारण बनता है। मैं इस कोड के लिए पासिंग यूनिट टेस्ट कैसे लिख सकता हूं? क्या यह उम्मीद की जाती है कि सामान्य ऑपरेशन में असफल होने वाले प्रत्येक डिफरर्ड को श्रृंखला के अंत में एक गलती होनी चाहिए जो इसे वापस कॉलबैक श्रृंखला पर रखे?बिना किसी त्रुटि के ट्विस्ट डिफरर्ड त्रुटियों को परीक्षण के साथ परीक्षण किया जा सकता है?

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

उदाहरण उदाहरण कोड का परीक्षण:

from twisted.trial import unittest 
from twisted.internet import defer 

def get_dl(**kwargs): 
    "Return a DeferredList with a failure and any kwargs given." 
    return defer.DeferredList(
     [defer.succeed(True), defer.fail(ValueError()), defer.succeed(True)], 
     **kwargs) 

def two_deferreds(): 
    "Create a failing Deferred, and create and return a succeeding Deferred." 
    d = defer.fail(ValueError()) 
    return defer.succeed(True) 


class DeferredChainTest(unittest.TestCase): 

    def check_success(self, result): 
     "If we're called, we're on the callback chain."   
     self.fail() 

    def check_error(self, failure): 
     """ 
     If we're called, we're on the errback chain. 
     Return to put us back on the callback chain. 
     """ 
     return True 

    def check_error_fail(self, failure): 
     """ 
     If we're called, we're on the errback chain. 
     """ 
     self.fail()   

    # This fails after all callbacks and errbacks have been run, with the 
    # ValueError from the failed defer, even though we're 
    # not on the errback chain. 
    def test_plain(self): 
     """ 
     Test that a DeferredList without arguments is on the callback chain. 
     """ 
     # check_error_fail asserts that we are on the callback chain. 
     return get_dl().addErrback(self.check_error_fail) 

    # This fails after all callbacks and errbacks have been run, with the 
    # ValueError from the failed defer, even though we're 
    # not on the errback chain. 
    def test_fire(self): 
     """ 
     Test that a DeferredList with fireOnOneErrback errbacks on failure, 
     and that an errback puts it back on the callback chain. 
     """ 
     # check_success asserts that we don't callback. 
     # check_error_fail asserts that we are on the callback chain. 
     return get_dl(fireOnOneErrback=True).addCallbacks(
      self.check_success, self.check_error).addErrback(
      self.check_error_fail) 

    # This succeeds. 
    def test_consume(self): 
     """ 
     Test that a DeferredList with consumeErrors errbacks on failure, 
     and that an errback puts it back on the callback chain. 
     """ 
     # check_error_fail asserts that we are on the callback chain. 
     return get_dl(consumeErrors=True).addErrback(self.check_error_fail) 

    # This succeeds. 
    def test_fire_consume(self): 
     """ 
     Test that a DeferredList with fireOnOneCallback and consumeErrors 
     errbacks on failure, and that an errback puts it back on the 
     callback chain. 
     """ 
     # check_success asserts that we don't callback. 
     # check_error_fail asserts that we are on the callback chain. 
     return get_dl(fireOnOneErrback=True, consumeErrors=True).addCallbacks(
      self.check_success, self.check_error).addErrback(
      self.check_error_fail) 

    # This fails after all callbacks and errbacks have been run, with the 
    # ValueError from the failed defer, even though we're 
    # not on the errback chain. 
    def test_two_deferreds(self): 
     # check_error_fail asserts that we are on the callback chain.   
     return two_deferreds().addErrback(self.check_error_fail) 

उत्तर

15

इस प्रश्न से संबंधित परीक्षण के बारे में दो महत्वपूर्ण बातें कर रहे हैं।

सबसे पहले, चलने के दौरान विफलता लॉग होने पर एक परीक्षण विधि पास नहीं होगी। असफलता के साथ एकत्रित कचरे वाले डिफरर्ड विफलता को लॉग इन करने का कारण बनते हैं।

दूसरा, एक परीक्षण विधि जो एक डिफर्ड लौटाती है, अगर विफलता विफलता के साथ आग लगती है तो वह पास नहीं होगी।

इसका मतलब है कि इन परीक्षणों के न पारित कर सकते हैं:

def test_logit(self): 
    defer.fail(Exception("oh no")) 

def test_returnit(self): 
    return defer.fail(Exception("oh no")) 

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

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

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

तो, इससे निपटने के लिए दो औजारों का अधिक उपयोगी TestCase.assertFailure है।

def test_returnit(self): 
    d = defer.fail(ValueError("6 is a bad value")) 
    return self.assertFailure(d, ValueError) 

यह परीक्षण क्योंकि d एक विफलता एक ValueError लपेटकर के साथ आग करता है पारित करेंगे: इस परीक्षण है कि एक स्थगित है कि एक विफलता के साथ आग जा रहा है वापस जाने के लिए चाहते हैं के लिए एक सहायक है। यदि d सफलता परिणाम के साथ या किसी अन्य अपवाद प्रकार को विफल करने में विफलता के साथ निकाल दिया गया था, तो परीक्षण अभी भी विफल हो जाएगा।

अगला, TestCase.flushLoggedErrors है। यह तब होता है जब आप किसी त्रुटि को लॉग करने के लिए पर एपीआई का परीक्षण कर रहे हैं। आखिरकार, कभी-कभी आप एक व्यवस्थापक को सूचित करना चाहते हैं कि कोई समस्या है।

def test_logit(self): 
    defer.fail(ValueError("6 is a bad value")) 
    gc.collect() 
    self.assertEquals(self.flushLoggedErrors(ValueError), 1) 

इस की मदद से आप असफलताओं जो लॉग इन कर ली सुनिश्चित करें कि आपके प्रवेश कोड को ठीक से काम कर रहा है का निरीक्षण किया। यह परीक्षणों को भी बताता है कि आपके द्वारा फ़्लश किए गए चीजों के बारे में चिंता न करें, इसलिए वे अब परीक्षण विफल होने का कारण नहीं बनेंगे। (gc.collect() कॉल वहां है क्योंकि त्रुटि को तब तक लॉग नहीं किया जाता है जब तक डिफरर्ड कचरा एकत्र नहीं किया जाता है। सीपीथॉन पर, यह जीसी व्यवहार की गणना करने के संदर्भ में तुरंत कचरा होगा। हालांकि, ज्योथन या पीपी या किसी अन्य पायथन रनटाइम पर संदर्भ गणना के बिना, आप उस पर भरोसा नहीं कर सकते हैं।)

इसके अलावा, चूंकि कचरा संग्रह किसी भी समय बहुत अधिक हो सकता है, तो आप कभी-कभी पाते हैं कि आपके परीक्षणों में से एक विफल रहता है क्योंकि एक त्रुटि द्वारा बनाए गए डिफरर्ड द्वारा लॉग इन किया जाता है बाद में परीक्षण के निष्पादन के दौरान पहले परीक्षण कचरा होता है। यह बहुत अधिक मतलब है कि आपका एरर हैंडलिंग कोड किसी भी तरह से अधूरा है - आपको कोई गलती याद आ रही है, या आप कहीं से दो डिफर्ड को चेन करने में असफल रहे हैं, या आप वास्तव में समाप्त होने वाले कार्य से पहले अपनी टेस्ट विधि समाप्त कर रहे हैं - लेकिन जिस तरह से त्रुटि की सूचना दी जाती है, कभी-कभी अपमानजनक कोड को ट्रैक करना मुश्किल हो जाता है। परीक्षण --force-gc विकल्प इसके साथ मदद कर सकता है। यह परीक्षण को प्रत्येक टेस्ट विधि के बीच कचरा कलेक्टर का आह्वान करने का कारण बनता है। इससे आपके परीक्षणों में काफी कमी आएगी, लेकिन यह परीक्षण के खिलाफ त्रुटि को लॉग इन करना चाहिए जो वास्तव में इसे ट्रिगर कर रहा है, मनमाने ढंग से बाद में परीक्षण नहीं।

+0

शानदार उत्तर, लेकिन आप '--force-gc' का भी उल्लेख करना चाहेंगे। – Glyph

+0

अच्छी कॉल, जोड़ा गया। –

+0

यह विफलता उदाहरण के साथ log.err को कॉल करते समय भी होता है, सही? – Chris

संबंधित मुद्दे