2013-09-29 7 views
19

मैं अपना पहला आईओएस यूनिट परीक्षण (एक्सकोड 5, आईओएस 6) लिख रहा हूं और पाया कि यूनिट परीक्षणों के परिणाम हाल ही में सिम्युलेटर में किए गए कार्यों के आधार पर भिन्न होते हैं। जैसे मैं सिम्युलेटर में अपनी संपर्क सूची में किसी उपयोगकर्ता पर क्लिक करता हूं, और अब उपयोगकर्ता डिफॉल्ट में मेरे "हालिया संपर्क" डेटा में पहले की तुलना में एक और ऑब्जेक्ट है, भले ही मैं यूनिट परीक्षण चला रहा हूं।एनएसयूसर डीफॉल्ट इकाई परीक्षणों के लिए साफ स्लेट नहीं होना चाहिए?

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

यहां संबंधित प्रश्नों को देखने से, मुझे कुछ संभावित उत्तर दिखाई देते हैं जिनसे मैं खुश नहीं हूं।

  • यूनिट परीक्षणों के लिए उपयोगकर्ता उपयोगकर्ता डिफ़ॉल्ट करें! मुझे कई मौजूदा वर्गों को संशोधित करना होगा ताकि मैं उस नकली इंजेक्ट कर सकूं।
  • एक सेटअप विधि में UserDefaults को साफ़ या अनुकूलित करें! लेकिन फिर मैन्युअल परीक्षण में श्रमिक रूप से बनाया गया मेरा डेटा चला जाएगा।
  • एक सेटअप में उपयोगकर्ता डिफ़ॉल्ट को साफ़ या अनुकूलित करें तो उन मानों को आंसू में पुनर्स्थापित करें! आउच।

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

  • क्या मुझे उपयोगकर्ता परीक्षण को यूनिट टेस्ट रन के माध्यम से विज्ञापन-प्रसार सिम्युलेटर परीक्षण से जारी रखने के तरीके के बारे में कुछ वांछनीय याद आ रही है?
  • क्या इसे ठीक करने के लिए एक कॉन्फ़िगर करने योग्य तरीका है, यूनिट परीक्षण लक्ष्य को मैन्युअल रूप से परीक्षण करने के लिए सिम्युलेटर का उपयोग करते समय यूनिटडिफॉल्ट के लिए अलग-अलग संग्रहण स्थान रखने के लिए कुछ तरीका कहें?
  • विफल होने पर, कोड में ऐसा करने का एक शानदार तरीका है?
  • उदाहरण के लिए, मेरे पास XAppestCase से MyAppTestCase ऑब्जेक्ट प्राप्त हो सकता है और हमेशा सेट अप करने के लिए setUp और tearDown विधियों को ओवरराइड कर सकता है और फिर UserDefaults को पुनर्स्थापित कर सकता है। यह एक अच्छा विचार है?
+0

आपको यूनिट को डिफ़ॉल्ट रूप से परीक्षण नहीं करना चाहिए क्योंकि ऐसा कुछ है जिस पर आपका कोई प्रत्यक्ष नियंत्रण नहीं है। एक इकाई परीक्षण को परमाणु सॉफ्टवेयर इकाई पर ही काम करना चाहिए -> कोई अन्य कक्षाएं या सेवाएं शामिल नहीं होनी चाहिए। आपका दृष्टिकोण एकीकरण परीक्षण की तरह बहुत अधिक लगता है। उत्तरार्द्ध आमतौर पर मॉक अप इंटरफेस का उपयोग करेगा। – Till

+4

मुझे लगता है कि आपने मेरे प्रश्न को गलत समझा। मैं कुछ ऐसी चीजों को खींचने के बिना यूनिट परीक्षण कैसे करूं जिन पर मेरा कोई प्रत्यक्ष नियंत्रण नहीं है? मैं वास्तविक मूल्यों के साथ उपयोगकर्ता डिफ़ॉल्ट में खींचने का इरादा नहीं था। – LisaD

+0

एक और समाधान ओसीएमॉक जैसे अलगाव ढांचे का उपयोग करना होगा। NSUserDefaults प्रकार की संपत्ति के रूप में अपनी कक्षा में एक तथाकथित सीम जोड़ें। परीक्षा के तहत कक्षा को मानक संपत्ति में संग्रहीत मानक यूज़र डीफॉल्ट के साथ शुरू किया जाएगा। अपने परीक्षण में आप डिफ़ॉल्ट वस्तु को नकली/स्टब के साथ ओवरराइट कर सकते हैं। उचित और verbose उत्तर के लिए –

उत्तर

18

नामित सूट का उपयोग like in this answer मेरे लिए अच्छा काम किया। परीक्षण के लिए उपयोग किए गए उपयोगकर्ता डिफ़ॉल्ट को हटाकर func tearDown() में भी किया जा सकता है।

class MyTest : XCTestCase { 
    var userDefaults: UserDefaults? 
    let userDefaultsSuiteName = "TestDefaults" 

    override func setUp() { 
     super.setUp() 
     UserDefaults().removePersistentDomain(forName: userDefaultsSuiteName) 
     userDefaults = UserDefaults(suiteName: userDefaultsSuiteName) 
    } 
} 
+2

यह मैंने देखा है कि समाधान को साफ करने के सबसे नज़दीक है। बहुत बुरा मैं आईओएस के लिए प्रोग्रामिंग नहीं कर रहा हूँ! – LisaD

+1

मुझे यह जानना अच्छा लगेगा कि इस स्वीकृत उत्तर में व्यक्तिगत रूप से कोई अपवर्तक क्यों नहीं है। – Hyperbole

+0

@ हाइपरबोले यह उत्तर उत्तर के लिए सबसे अधिक वोट किए गए 2 साल से कम है। यह शायद मूल स्वीकृत उत्तर नहीं था और अभी भी पकड़ रहा है। इसे दो और साल दें, और यह लीड में हो सकता है :-) – Benjohn

13

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

कुछ लोग तर्क देंगे कि यूनिट-टेस्टेबल ऑब्जेक्ट्स NSUserDefaults जैसे सिंगलेट्स पर भरोसा नहीं करना चाहिए या मेरी वैश्विक "कॉन्फ़िगरेशन" ऑब्जेक्ट की अनुशंसा नहीं है। इसके बजाय, सभी विन्यास init पर इंजेक्शन किया जाना चाहिए। प्रैक्टिस में, स्टोरीबोर्ड के साथ बातचीत करते समय मुझे यह बहुत अधिक सिरदर्द पैदा करने के लिए मिलता है, लेकिन यह उन जगहों पर विचार करने योग्य है जहां यह उपयोगी हो सकता है।

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

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

+1

+1 - अमूर्त परत वास्तव में ऐसा कुछ है जो एक महान दृष्टिकोण है। मैंने पाया कि यूनिट परीक्षण अक्सर मुझे बेहतर सॉफ्टवेयर बनाने के लिए मजबूर करते हैं क्योंकि मुझे ऐसी चीजों को बनाने के लिए मजबूर किया जाता है जो उचित इकाई परीक्षण की अनुमति देता है - इस तथ्य के बावजूद कि ये परीक्षण बहुत उपयोगी हैं;)। – Till

+0

मैं उम्मीद कर रहा था कि यह कॉन्फ़िगरेशन/पर्यावरण समस्या हो, जिस तरह से यह अन्य ढांचे में है (उदाहरण के लिए, आरओआर डिफ़ॉल्ट प्रत्येक रन को पूरी तरह से साफ डीबी बनाना है)। विफल होने पर, मैं एक अमूर्त परत जोड़ने के बारे में आपसे सहमत हूं। धन्यवाद। – LisaD

+1

आपकी परियोजना के किसी बिंदु पर सिंगलटन स्विजल अनिवार्य हो सकता है। http://twobitlabs.com/2011/02/mocking-singletons-with-ocmock/ – Francescu

19

उपलब्ध आईओएस 7/10,9

बल्कि standardUserDefaults का उपयोग करने से आप कुछ कोड के साथ अपने परीक्षण लोड करने के लिए

[[NSUserDefaults alloc] initWithSuiteName:@"SomeOtherTests"]; 

इस मिलकर एक सूट नाम का उपयोग कर सकते हैं उचित से SomeOtherTests.plist फ़ाइल को हटाने के लिए setUp में निर्देशिका वांछित परिणाम संग्रहित करेगा।

आपको किसी भी ऑब्जेक्ट को अपनी डिफ़ॉल्ट ऑब्जेक्ट्स लेने के लिए डिज़ाइन करना होगा ताकि परीक्षण से कोई साइड इफेक्ट न हो।

+0

यह दृष्टिकोण के साथ मैं चले गए है और यह वास्तव में अच्छी तरह से काम करता है। मेरे पास एक सेवा है जो उपयोगकर्ता डिफ़ॉल्ट का उपयोग करती है और इसमें एक 'init' विधि है जो उपयोगकर्ता को "इंजेक्शन" होने के लिए डिफ़ॉल्ट रूप से अनुमति देता है। यह यूनिट को सेवा का परीक्षण करना आसान बनाता है। ** उपयोगकर्ता डिफ़ॉल्ट से सेटिंग्स को शुद्ध करने के लिए **, विधि 'removePersistentDomainForName:' विधि का उपयोग करें। प्लेस्ट फ़ाइलों के साथ डिस्क पर बंदरगाह से यह आसान है। – Benjohn

1

आप आसानी से & मुख्य बंडल के पहचानकर्ता है, जो है क्या [[NSUserDefaults standardUserDefaults] setObject:forKey:] को लिखते हैं के लिए लगातार डोमेन बहाल बचा सकता है। उदाहरण के लिए,

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 
NSDictionary *originalValues = [defaults persistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]]; 

// do stuff, possibly [defaults removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]] 
// or using setPersistentDomain: to substitute a dictionary of mock values and test against that 

[defaults setPersistentDomain:originalValues forName:[[NSBundle mainBundle] bundleIdentifier]]; 

तुम भी [[NSUserDefaults standardUserDefaults] volatileDomainForName:NSRegistrationDomain] उपयोग कर सकते हैं आप सामान आप सभी -registerDefaults: कॉल का उपयोग रजिस्टर (कम से कम है कि जहां इकाई परीक्षण है अप करने के लिए समाप्त हो गया है किसी भी कोड के लिए की एक संयुक्त शब्दकोश का उपयोग करना चाहते हैं निश्चित रूप से शुरू किया)।

1

मुझे एक नया बनाना पसंद है इसलिए कोई संलयन नहीं है।

import XCTest 

extension UserDefaults { 
    private static var index = 0 
    static func createCleanForTest(label: StaticString = #file) -> UserDefaults { 
     index += 1 
     let suiteName = "UnitTest-UserDefaults-\(label)-\(index)" 
     UserDefaults().removePersistentDomain(forName: suiteName) 
     return UserDefaults(suiteName: suiteName)! 
    } 
} 

class MyTest: XCTestCase { 

    func testOne() { 
     let userDefaults = UserDefaults.createCleanForTest() 
     XCTAssertFalse(userDefaults.bool(forKey: "foo")) 
     userDefaults.set(true, forKey: "foo") 
     XCTAssertTrue(userDefaults.bool(forKey: "foo")) 
    } 

    func testTwo() { 
     let userDefaults = UserDefaults.createCleanForTest() 
     XCTAssertFalse(userDefaults.bool(forKey: "foo")) 
     userDefaults.set(true, forKey: "foo") 
     XCTAssertTrue(userDefaults.bool(forKey: "foo")) 
    } 
} 
संबंधित मुद्दे