टेस्ट डबल और डिपेंडेंसी इंजेक्शन के बारे में जानकारी

यह कोडलैब, Kotlin में ऐडवांस Android कोर्स का हिस्सा है. अगर इस कोर्स के कोडलैब को क्रम से पूरा किया जाता है, तो आपको सबसे ज़्यादा फ़ायदा मिलेगा. हालांकि, ऐसा करना ज़रूरी नहीं है. कोर्स के सभी कोडलब, Advanced Android in Kotlin कोडलब के लैंडिंग पेज पर दिए गए हैं.

परिचय

यह दूसरा टेस्टिंग कोडलैब, टेस्ट डबल के बारे में है: Android में इनका इस्तेमाल कब करना चाहिए और डिपेंडेंसी इंजेक्शन, सर्विस लोकेटर पैटर्न, और लाइब्रेरी का इस्तेमाल करके इन्हें कैसे लागू करना चाहिए. ऐसा करने से, आपको यह लिखने का तरीका पता चलेगा:

  • रिपॉज़िटरी यूनिट टेस्ट
  • फ़्रैगमेंट और व्यूमॉडल इंटिग्रेशन की जांच
  • फ़्रैगमेंट नेविगेशन टेस्ट

आपको पहले से क्या पता होना चाहिए

आपको इनके बारे में जानकारी होनी चाहिए:

  • Kotlin प्रोग्रामिंग लैंग्वेज
  • पहले कोडलैब में, टेस्टिंग के इन कॉन्सेप्ट के बारे में बताया गया है: Android पर यूनिट टेस्ट लिखना और उन्हें चलाना, JUnit, Hamcrest, AndroidX test, Robolectric का इस्तेमाल करना, और LiveData की टेस्टिंग करना
  • Android Jetpack की ये मुख्य लाइब्रेरी: ViewModel, LiveData और Navigation Component
  • ऐप्लिकेशन का आर्किटेक्चर, ऐप्लिकेशन के आर्किटेक्चर की गाइड और Android के बुनियादी कोडलैब के पैटर्न के मुताबिक होना चाहिए
  • Android पर कोरूटीन की बुनियादी बातें

आपको क्या सीखने को मिलेगा

  • टेस्टिंग की रणनीति बनाने का तरीका
  • टेस्ट डबल, जैसे कि फ़ेक और मॉक बनाने और इस्तेमाल करने का तरीका
  • यूनिट और इंटिग्रेशन टेस्ट के लिए, Android पर मैन्युअल डिपेंडेंसी इंजेक्शन का इस्तेमाल कैसे करें
  • Service Locator Pattern को लागू करने का तरीका
  • रिपॉज़िटरी, फ़्रैगमेंट, व्यू मॉडल, और नेविगेशन कॉम्पोनेंट की जांच करने का तरीका

इन लाइब्रेरी और कोड कॉन्सेप्ट का इस्तेमाल किया जाएगा:

आपको क्या करना होगा

  • टेस्ट डबल और डिपेंडेंसी इंजेक्शन का इस्तेमाल करके, किसी रिपॉज़िटरी के लिए यूनिट टेस्ट लिखें.
  • टेस्ट डबल और डिपेंडेंसी इंजेक्शन का इस्तेमाल करके, व्यू मॉडल के लिए यूनिट टेस्ट लिखें.
  • Espresso यूज़र इंटरफ़ेस (यूआई) टेस्टिंग फ़्रेमवर्क का इस्तेमाल करके, फ़्रैगमेंट और उनके व्यू मॉडल के लिए इंटिग्रेशन टेस्ट लिखें.
  • Mockito और Espresso का इस्तेमाल करके, नेविगेशन टेस्ट लिखें.

इस कोडलैब सीरीज़ में, आपको TO-DO Notes ऐप्लिकेशन के साथ काम करना होगा. इस ऐप्लिकेशन की मदद से, पूरे किए जाने वाले टास्क लिखे जा सकते हैं. साथ ही, उन्हें सूची में दिखाया जा सकता है. इसके बाद, उन्हें 'पूरा हो गया' या 'पूरा नहीं हुआ' के तौर पर मार्क किया जा सकता है, फ़िल्टर किया जा सकता है या मिटाया जा सकता है.

यह ऐप्लिकेशन Kotlin में लिखा गया है. इसमें कुछ स्क्रीन हैं. यह Jetpack कॉम्पोनेंट का इस्तेमाल करता है और ऐप्लिकेशन के आर्किटेक्चर के लिए गाइड में दिए गए आर्किटेक्चर का पालन करता है. इस ऐप्लिकेशन की जांच करने का तरीका जानने के बाद, उन ऐप्लिकेशन की जांच की जा सकती है जो एक ही लाइब्रेरी और आर्किटेक्चर का इस्तेमाल करते हैं.

कोड डाउनलोड करें

शुरू करने के लिए, कोड डाउनलोड करें:

ज़िप फ़ाइल डाउनलोड करें

इसके अलावा, कोड के लिए Github रिपॉज़िटरी को क्लोन किया जा सकता है:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout end_codelab_1

यहां दिए गए निर्देशों का पालन करके, कोड के बारे में जानें.

पहला चरण: सैंपल ऐप्लिकेशन चलाना

TO-DO ऐप्लिकेशन डाउनलोड करने के बाद, इसे Android Studio में खोलें और चलाएं. इसे कंपाइल किया जाना चाहिए. ऐप्लिकेशन को एक्सप्लोर करने के लिए, यह तरीका अपनाएं:

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

दूसरा चरण: सैंपल ऐप्लिकेशन का कोड एक्सप्लोर करना

TO-DO ऐप्लिकेशन, लोकप्रिय आर्किटेक्चर ब्लूप्रिंट टेस्टिंग और आर्किटेक्चर सैंपल पर आधारित है. इसमें सैंपल के रिएक्टिव आर्किटेक्चर वर्शन का इस्तेमाल किया गया है. ऐप्लिकेशन, ऐप्लिकेशन के आर्किटेक्चर की गाइड में दिए गए आर्किटेक्चर का पालन करता है. यह फ़्रैगमेंट, रिपॉज़िटरी, और Room के साथ ViewModels का इस्तेमाल करता है. अगर आपने नीचे दिए गए किसी उदाहरण का इस्तेमाल किया है, तो इस ऐप्लिकेशन का आर्किटेक्चर भी ऐसा ही है:

किसी एक लेयर के लॉजिक को गहराई से समझने के बजाय, ऐप्लिकेशन के सामान्य आर्किटेक्चर को समझना ज़्यादा ज़रूरी है.

यहां आपको मिलने वाले पैकेज की खास जानकारी दी गई है:

पैकेज: com.example.android.architecture.blueprints.todoapp

.addedittask

टास्क जोड़ने या उसमें बदलाव करने वाली स्क्रीन: टास्क जोड़ने या उसमें बदलाव करने के लिए यूज़र इंटरफ़ेस (यूआई) लेयर का कोड.

.data

डेटा लेयर: यह टास्क की डेटा लेयर से जुड़ी होती है. इसमें डेटाबेस, नेटवर्क, और रिपॉज़िटरी कोड होता है.

.statistics

आंकड़ों की स्क्रीन: आंकड़ों की स्क्रीन के लिए यूज़र इंटरफ़ेस लेयर का कोड.

.taskdetail

टास्क की जानकारी वाली स्क्रीन: किसी एक टास्क के लिए यूज़र इंटरफ़ेस (यूआई) लेयर का कोड.

.tasks

टास्क स्क्रीन: सभी टास्क की सूची के लिए यूज़र इंटरफ़ेस (यूआई) लेयर कोड.

.util

यूटिलिटी क्लास: ये ऐसी क्लास होती हैं जिनका इस्तेमाल ऐप्लिकेशन के अलग-अलग हिस्सों में किया जाता है. उदाहरण के लिए, कई स्क्रीन पर इस्तेमाल किए जाने वाले स्वाइप रिफ़्रेश लेआउट के लिए.

डेटा लेयर (.data)

इस ऐप्लिकेशन में, remote पैकेज में एक सिम्युलेटेड नेटवर्किंग लेयर और local पैकेज में एक डेटाबेस लेयर शामिल है. आसानी के लिए, इस प्रोजेक्ट में नेटवर्किंग लेयर को सिर्फ़ HashMap के साथ सिम्युलेट किया गया है. इसमें असली नेटवर्क अनुरोधों के बजाय, कुछ समय के लिए देरी की जाती है.

DefaultTasksRepository नेटवर्किंग लेयर और डेटाबेस लेयर के बीच तालमेल बिठाता है. साथ ही, यह यूज़र इंटरफ़ेस (यूआई) लेयर को डेटा दिखाता है.

यूज़र इंटरफ़ेस (यूआई) लेयर ( .addedittask, .statistics, .taskdetail, .tasks)

यूज़र इंटरफ़ेस (यूआई) लेयर के हर पैकेज में एक फ़्रैगमेंट और एक व्यू मॉडल होता है. साथ ही, यूज़र इंटरफ़ेस (यूआई) के लिए ज़रूरी अन्य क्लास भी होती हैं. जैसे, टास्क लिस्ट के लिए अडैप्टर. TaskActivity एक ऐसी गतिविधि है जिसमें सभी फ़्रैगमेंट शामिल होते हैं.

नेविगेशन

ऐप्लिकेशन के नेविगेशन को नेविगेशन कॉम्पोनेंट से कंट्रोल किया जाता है. इसे nav_graph.xml फ़ाइल में तय किया जाता है. Event क्लास का इस्तेमाल करके, व्यू मॉडल में नेविगेशन ट्रिगर किया जाता है. व्यू मॉडल यह भी तय करते हैं कि कौनसे आर्ग्युमेंट पास करने हैं. फ़्रैगमेंट, Event को देखते हैं और स्क्रीन के बीच नेविगेशन की सुविधा देते हैं.

इस कोडलैब में, टेस्ट डबल और डिपेंडेंसी इंजेक्शन का इस्तेमाल करके, रिपॉज़िटरी, व्यू मॉडल, और फ़्रैगमेंट की जांच करने का तरीका बताया गया है. इनके बारे में जानने से पहले, यह समझना ज़रूरी है कि इन टेस्ट को कैसे और क्यों लिखा जाएगा.

इस सेक्शन में, टेस्टिंग के कुछ सामान्य सबसे सही तरीके बताए गए हैं. ये तरीके Android पर भी लागू होते हैं.

टेस्टिंग पिरामिड

टेस्टिंग की रणनीति बनाते समय, टेस्टिंग से जुड़े इन तीन पहलुओं पर ध्यान दें:

  • स्कोप—टेस्ट, कोड के कितने हिस्से को कवर करता है? टेस्ट, किसी एक तरीके पर, पूरे ऐप्लिकेशन पर या इनके बीच में कहीं भी चलाए जा सकते हैं.
  • स्पीड—टेस्ट कितनी तेज़ी से होता है? स्पीड टेस्ट में कुछ मिलीसेकंड से लेकर कई मिनट लग सकते हैं.
  • फ़िडेलिटी—यह टेस्ट "असल दुनिया" में कितना काम करता है? उदाहरण के लिए, अगर आपको जिस कोड की जांच करनी है उसके किसी हिस्से को नेटवर्क अनुरोध करना है, तो क्या जांच करने वाला कोड असल में यह नेटवर्क अनुरोध करता है या यह नतीजे को फ़र्ज़ी बनाता है? अगर टेस्ट में नेटवर्क से बातचीत की जाती है, तो इसका मतलब है कि यह ज़्यादा फ़िडेलिटी वाला है. हालांकि, इस टेस्ट को पूरा होने में ज़्यादा समय लग सकता है. साथ ही, नेटवर्क काम न करने पर गड़बड़ियां हो सकती हैं या इसका इस्तेमाल महंगा पड़ सकता है.

इन पहलुओं के बीच कुछ अंतर हैं. उदाहरण के लिए, स्पीड और फ़िडेलिटी के बीच समझौता होता है. आम तौर पर, टेस्ट जितना तेज़ होता है, फ़िडेलिटी उतनी ही कम होती है. इसके उलट, टेस्ट जितना धीमा होता है, फ़िडेलिटी उतनी ही ज़्यादा होती है. ऑटोमेटेड टेस्ट को इन तीन कैटगरी में बांटा जाता है:

  • यूनिट टेस्ट—ये ऐसे टेस्ट होते हैं जो किसी एक क्लास पर फ़ोकस करते हैं. आम तौर पर, ये उस क्लास के किसी एक तरीके पर फ़ोकस करते हैं. अगर कोई यूनिट टेस्ट फ़ेल हो जाती है, तो आपको पता चल सकता है कि आपके कोड में समस्या कहां है. इनकी फ़िडेलिटी कम होती है, क्योंकि असल दुनिया में आपका ऐप्लिकेशन, एक तरीके या क्लास के एक्ज़ीक्यूशन से कहीं ज़्यादा काम करता है. ये इतनी तेज़ी से काम करते हैं कि जब भी कोड में बदलाव किया जाता है, तब ये तुरंत काम करते हैं. ये टेस्ट, ज़्यादातर स्थानीय तौर पर चलाए जाने वाले टेस्ट होंगे. ये test सोर्स सेट में शामिल होंगे. उदाहरण: व्यू मॉडल और रिपॉज़िटरी में किसी एक तरीके को टेस्ट करना.
  • इंटिग्रेशन टेस्ट—इन टेस्ट से यह पता चलता है कि कई क्लास एक साथ इस्तेमाल करने पर, उम्मीद के मुताबिक काम कर रही हैं या नहीं. इंटिग्रेशन टेस्ट को स्ट्रक्चर करने का एक तरीका यह है कि वे किसी एक सुविधा की जांच करें. जैसे, किसी टास्क को सेव करने की सुविधा. ये यूनिट टेस्ट की तुलना में, कोड के ज़्यादा हिस्से की जांच करते हैं. हालांकि, इन्हें पूरी तरह से सटीक होने के बजाय, तेज़ी से काम करने के लिए ऑप्टिमाइज़ किया जाता है. इन्हें स्थिति के हिसाब से, स्थानीय तौर पर या इंस्ट्रुमेंटेशन टेस्ट के तौर पर चलाया जा सकता है. उदाहरण: एक फ़्रैगमेंट और व्यू मॉडल पेयर की सभी सुविधाओं की जांच करना.
  • एंड-टू-एंड टेस्ट (E2e)—एक साथ काम करने वाली सुविधाओं के कॉम्बिनेशन की जांच करें. ये ऐप्लिकेशन के ज़्यादातर हिस्सों की जांच करते हैं और असल इस्तेमाल की नकल करते हैं. इसलिए, आम तौर पर ये धीरे-धीरे काम करते हैं. इनकी फ़िडेलिटी सबसे ज़्यादा होती है. इनसे पता चलता है कि आपका ऐप्लिकेशन पूरी तरह से काम करता है. आम तौर पर, ये इंस्ट्रुमेंटेड टेस्ट (androidTest सोर्स सेट में) होते हैं
    उदाहरण के लिए: पूरे ऐप्लिकेशन को शुरू करना और कुछ सुविधाओं को एक साथ टेस्ट करना.

इन टेस्ट के सुझाए गए अनुपात को अक्सर पिरामिड के तौर पर दिखाया जाता है. इसमें ज़्यादातर टेस्ट यूनिट टेस्ट होते हैं.

आर्किटेक्चर और टेस्टिंग

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

ऐप्लिकेशन के लॉजिक को कई तरीकों और क्लास में बांटना बेहतर होगा, ताकि हर हिस्से को अलग से टेस्ट किया जा सके. आर्किटेक्चर, आपके कोड को अलग-अलग हिस्सों में बांटने और व्यवस्थित करने का एक तरीका है. इससे यूनिट और इंटिग्रेशन टेस्टिंग को आसानी से किया जा सकता है. आपको जिस'टू-डू' ऐप्लिकेशन को टेस्ट करना है वह एक खास आर्किटेक्चर पर काम करता है:



इस लेसन में, आपको यह पता चलेगा कि ऊपर दिए गए आर्किटेक्चर के हिस्सों को अलग-अलग करके कैसे टेस्ट किया जाता है:

  1. सबसे पहले, रिपॉज़िटरी की यूनिट टेस्ट की जाएगी.
  2. इसके बाद, व्यू मॉडल में टेस्ट डबल का इस्तेमाल किया जाएगा. यह व्यू मॉडल की यूनिट टेस्टिंग और इंटिग्रेशन टेस्टिंग के लिए ज़रूरी है.
  3. इसके बाद, आपको फ़्रैगमेंट और उनके व्यू मॉडल के लिए, इंटिग्रेशन टेस्ट लिखने का तरीका बताया जाएगा.
  4. आखिर में, आपको इंटिग्रेशन टेस्ट लिखने का तरीका बताया जाएगा. इनमें नेविगेशन कॉम्पोनेंट शामिल होगा.

शुरू से आखिर तक की जांच के बारे में अगले लेसन में बताया जाएगा.

जब किसी क्लास के किसी हिस्से (कोई तरीका या तरीकों का छोटा कलेक्शन) के लिए यूनिट टेस्ट लिखा जाता है, तो आपका मकसद सिर्फ़ उस क्लास में मौजूद कोड की जांच करना होता है.

किसी खास क्लास या क्लास में सिर्फ़ कोड की टेस्टिंग करना मुश्किल हो सकता है. आइए एक उदाहरण देखें. main सोर्स सेट में data.source.DefaultTaskRepository क्लास खोलें. यह ऐप्लिकेशन के लिए रिपॉज़िटरी है. साथ ही, यह वह क्लास है जिसके लिए आपको अगली यूनिट टेस्ट लिखनी है.

आपका लक्ष्य सिर्फ़ उस क्लास के कोड की जांच करना है. हालांकि, काम करने के लिए DefaultTaskRepository को LocalTaskDataSource और RemoteTaskDataSource जैसी अन्य क्लास पर निर्भर रहना पड़ता है. इसे इस तरह भी कहा जा सकता है कि LocalTaskDataSource और RemoteTaskDataSource, DefaultTaskRepository की डिपेंडेंसी हैं.

इसलिए, DefaultTaskRepository में मौजूद हर तरीका, डेटा सोर्स क्लास में मौजूद तरीकों को कॉल करता है. ये तरीके, डेटाबेस में जानकारी सेव करने या नेटवर्क से कम्यूनिकेट करने के लिए, अन्य क्लास में मौजूद तरीकों को कॉल करते हैं.



उदाहरण के लिए, DefaultTasksRepo में इस तरीके को देखें.

    suspend fun getTasks(forceUpdate: Boolean = false): Result<List<Task>> {
        if (forceUpdate) {
            try {
                updateTasksFromRemoteDataSource()
            } catch (ex: Exception) {
                return Result.Error(ex)
            }
        }
        return tasksLocalDataSource.getTasks()
    }

getTasks, रिपॉज़िटरी को किए जाने वाले सबसे "बुनियादी" कॉल में से एक है. इस तरीके में, SQLite डेटाबेस से डेटा पढ़ना और नेटवर्क कॉल करना (updateTasksFromRemoteDataSource को कॉल करना) शामिल है. इसमें रिपॉज़िटरी कोड के अलावा और भी बहुत कुछ शामिल है.

रिपॉज़िटरी की जांच करना मुश्किल क्यों है, इसकी कुछ और वजहें यहां दी गई हैं:

  • इस रिपॉज़िटरी के लिए सबसे आसान टेस्ट करने के लिए भी, आपको डेटाबेस बनाने और उसे मैनेज करने के बारे में सोचना होगा. इससे "क्या यह लोकल या इंस्ट्रुमेंटेड टेस्ट होना चाहिए?" जैसे सवाल उठते हैं. साथ ही, यह भी सवाल उठता है कि क्या आपको AndroidX Test का इस्तेमाल करके, Android के सिम्युलेटेड एनवायरमेंट को पाना चाहिए.
  • नेटवर्किंग कोड जैसे कोड के कुछ हिस्सों को चलने में लंबा समय लग सकता है. इसके अलावा, कभी-कभी ये काम नहीं भी करते हैं. इससे लंबे समय तक चलने वाले और भरोसेमंद न होने वाले टेस्ट बन जाते हैं.
  • ऐसा हो सकता है कि आपकी जांचों से यह पता न चल पाए कि जांच के फ़ेल होने के लिए कौन सा कोड ज़िम्मेदार है. आपकी जांचें, नॉन-रिपॉज़िटरी कोड की जांच शुरू कर सकती हैं. इसलिए, उदाहरण के लिए, आपकी "रिपॉज़िटरी" यूनिट टेस्ट, डेटाबेस कोड जैसे किसी डिपेंडेंट कोड में समस्या की वजह से फ़ेल हो सकती हैं.

टेस्ट डबल

इस समस्या को हल करने के लिए, जब रिपॉज़िटरी की जांच की जा रही हो, तब असली नेटवर्किंग या डेटाबेस कोड का इस्तेमाल न करें. इसके बजाय, टेस्ट डबल का इस्तेमाल करें. टेस्ट डबल, क्लास का एक ऐसा वर्शन होता है जिसे खास तौर पर टेस्टिंग के लिए बनाया जाता है. इसका इस्तेमाल, टेस्ट में क्लास के असली वर्शन की जगह पर किया जाता है. यह कुछ ऐसा ही है जैसे स्टंट डबल एक ऐसा ऐक्टर होता है जो स्टंट करने में माहिर होता है. वह खतरनाक स्टंट के लिए, असली ऐक्टर की जगह काम करता है.

यहां टेस्ट डबल के कुछ टाइप दिए गए हैं:

फ़र्ज़ी

यह एक टेस्ट डबल है, जिसमें क्लास का "वर्किंग" इंप्लीमेंटेशन होता है. हालांकि, इसे इस तरह से लागू किया जाता है कि यह टेस्ट के लिए अच्छा हो, लेकिन प्रोडक्शन के लिए सही न हो.

Mock

यह एक टेस्ट डबल है. यह ट्रैक करता है कि इसके किन तरीकों को कॉल किया गया था. इसके बाद, यह इस बात पर निर्भर करता है कि इसके तरीकों को सही तरीके से कॉल किया गया है या नहीं.

Stub

यह एक टेस्ट डबल है, जिसमें कोई लॉजिक शामिल नहीं होता. यह सिर्फ़ वही वैल्यू दिखाता है जिसे दिखाने के लिए इसे प्रोग्राम किया गया है. उदाहरण के लिए, StubTaskRepository को इस तरह से प्रोग्राम किया जा सकता है कि वह getTasks से टास्क के कुछ कॉम्बिनेशन वापस कर सके.

डमी

एक टेस्ट डबल, जिसे पास किया जाता है, लेकिन इस्तेमाल नहीं किया जाता. जैसे, अगर आपको इसे सिर्फ़ पैरामीटर के तौर पर देना है. अगर आपके पास NoOpTaskRepository था, तो यह किसी भी तरीके से no कोड के साथ TaskRepository को लागू करेगा.

Spy

यह एक टेस्ट डबल है, जो कुछ अतिरिक्त जानकारी को भी ट्रैक करता है. उदाहरण के लिए, अगर आपने कोई SpyTaskRepository बनाया है, तो यह इस बात को ट्रैक कर सकता है कि addTask तरीके को कितनी बार कॉल किया गया.

टेस्ट डबल के बारे में ज़्यादा जानने के लिए, Testing on the Toilet: Know Your Test Doubles लेख पढ़ें.

Android में, सबसे ज़्यादा इस्तेमाल होने वाले टेस्ट डबल, फ़ेक और मॉक हैं.

इस टास्क में, आपको FakeDataSource टेस्ट डबल बनाना है, ताकि यूनिट टेस्ट DefaultTasksRepository को असल डेटा सोर्स से अलग किया जा सके.

पहला चरण: FakeDataSource क्लास बनाना

इस चरण में, आपको FakeDataSouce नाम की एक क्लास बनानी होगी. यह LocalDataSource और RemoteDataSource का टेस्ट डबल होगा.

  1. test सोर्स सेट में, राइट क्लिक करके New -> Package चुनें.

  1. डेटा पैकेज बनाएं. इसमें सोर्स पैकेज शामिल होना चाहिए.
  2. data/source पैकेज में FakeDataSource नाम की नई क्लास बनाएं.

दूसरा चरण: TasksDataSource इंटरफ़ेस लागू करना

अपनी नई क्लास FakeDataSource को टेस्ट डबल के तौर पर इस्तेमाल करने के लिए, यह ज़रूरी है कि वह अन्य डेटा सोर्स को बदल सके. ये डेटा सोर्स TasksLocalDataSource और TasksRemoteDataSource हैं.

  1. ध्यान दें कि ये दोनों TasksDataSource इंटरफ़ेस को कैसे लागू करते हैं.
class TasksLocalDataSource internal constructor(
    private val tasksDao: TasksDao,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksDataSource { ... }

object TasksRemoteDataSource : TasksDataSource { ... }
  1. FakeDataSource को TasksDataSource लागू करने के लिए:
class FakeDataSource : TasksDataSource {

}

Android Studio आपको यह सूचना देगा कि आपने TasksDataSource के लिए ज़रूरी तरीके लागू नहीं किए हैं.

  1. क्विक-फ़िक्स मेन्यू का इस्तेमाल करें और सदस्यों को लागू करें को चुनें.


  1. सभी तरीके चुनें और ठीक है दबाएं.

तीसरा चरण: FakeDataSource में getTasks तरीके को लागू करना

FakeDataSource, एक खास तरह का टेस्ट डबल है. इसे फ़ेक कहा जाता है. फ़ेक, टेस्ट डबल होता है. इसमें क्लास को "काम करने वाले" तरीके से लागू किया जाता है. हालांकि, इसे इस तरह से लागू किया जाता है कि यह टेस्ट के लिए अच्छा हो, लेकिन प्रोडक्शन के लिए सही न हो. "वर्किंग" का मतलब है कि क्लास, इनपुट के हिसाब से सही आउटपुट देगी.

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

FakeDataSource

  • इसकी मदद से, DefaultTasksRepository में कोड को टेस्ट किया जा सकता है. इसके लिए, किसी असली डेटाबेस या नेटवर्क पर निर्भर रहने की ज़रूरत नहीं होती.
  • टेस्ट के लिए "काफ़ी हद तक असली" लागू करने की सुविधा देता है.
  1. FakeDataSource कंस्ट्रक्टर को बदलकर, tasks नाम का var बनाएं. यह MutableList<Task>? है और इसकी डिफ़ॉल्ट वैल्यू, खाली बदलाव की जा सकने वाली सूची है.
class FakeDataSource(var tasks: MutableList<Task>? = mutableListOf()) : TasksDataSource { // Rest of class }


यह उन टास्क की सूची है जो डेटाबेस या सर्वर से मिले रिस्पॉन्स की तरह "दिखते" हैं. फ़िलहाल, हमारा मकसद रिपॉज़िटरी के getTasks तरीके की जांच करना है. इससे, डेटा सोर्स के getTasks, deleteAllTasks, और saveTask तरीके कॉल होते हैं.

इन तरीकों का फ़र्ज़ी वर्शन लिखो:

  1. getTasks लिखें: अगर tasks, null नहीं है, तो Success नतीजा दिखाएं. अगर tasks null है, तो Error नतीजा दिखाएं.
  2. deleteAllTasks लिखें: इससे टास्क की सूची मिट जाएगी.
  3. saveTask लिखें: टास्क को सूची में जोड़ें.

FakeDataSource के लिए लागू किए गए ये तरीके, यहां दिए गए कोड की तरह दिखते हैं.

override suspend fun getTasks(): Result<List<Task>> {
    tasks?.let { return Success(ArrayList(it)) }
    return Error(
        Exception("Tasks not found")
    )
}


override suspend fun deleteAllTasks() {
    tasks?.clear()
}

override suspend fun saveTask(task: Task) {
    tasks?.add(task)
}

ज़रूरत पड़ने पर, यहां इंपोर्ट स्टेटमेंट दिए गए हैं:

import com.example.android.architecture.blueprints.todoapp.data.Result
import com.example.android.architecture.blueprints.todoapp.data.Result.Error
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task

यह ठीक उसी तरह काम करता है जिस तरह लोकल और रिमोट डेटा सोर्स काम करते हैं.

इस चरण में, आपको मैन्युअल डिपेंडेंसी इंजेक्शन नाम की तकनीक का इस्तेमाल करना होगा, ताकि अभी-अभी बनाए गए फ़र्ज़ी टेस्ट डबल का इस्तेमाल किया जा सके.

मुख्य समस्या यह है कि आपके पास FakeDataSource है, लेकिन यह साफ़ तौर पर नहीं बताया गया है कि इसका इस्तेमाल टेस्ट में कैसे किया जाता है. इसे TasksRemoteDataSource और TasksLocalDataSource की जगह इस्तेमाल किया जाना चाहिए. हालांकि, ऐसा सिर्फ़ टेस्ट में होना चाहिए. TasksRemoteDataSource और TasksLocalDataSource, दोनों DefaultTasksRepository पर निर्भर हैं. इसका मतलब है कि DefaultTasksRepositories को चलाने के लिए, इन क्लास की ज़रूरत होती है या ये क्लास DefaultTasksRepositories पर "निर्भर" होती हैं.

फ़िलहाल, डिपेंडेंसी को DefaultTasksRepository के init तरीके में बनाया गया है.

DefaultTasksRepository.kt

class DefaultTasksRepository private constructor(application: Application) {

    private val tasksRemoteDataSource: TasksDataSource
    private val tasksLocalDataSource: TasksDataSource

   // Some other code

    init {
        val database = Room.databaseBuilder(application.applicationContext,
            ToDoDatabase::class.java, "Tasks.db")
            .build()

        tasksRemoteDataSource = TasksRemoteDataSource
        tasksLocalDataSource = TasksLocalDataSource(database.taskDao())
    }
    // Rest of class
}

DefaultTasksRepository में taskLocalDataSource और tasksRemoteDataSource बनाने और असाइन करने की वजह से, ये हार्ड-कोड किए जाते हैं. टेस्ट डबल को स्वैप करने का कोई तरीका नहीं है.

आपको इन डेटा सोर्स को हार्ड-कोड करने के बजाय, क्लास को देना है. डिपेंडेंसी उपलब्ध कराने को डिपेंडेंसी इंजेक्शन कहा जाता है. डिपेंडेंसी देने के अलग-अलग तरीके होते हैं. इसलिए, डिपेंडेंसी इंजेक्शन भी अलग-अलग तरह के होते हैं.

कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन की मदद से, कंस्ट्रक्टर में पास करके टेस्ट डबल को स्वैप किया जा सकता है.

कोई इंजेक्शन नहीं

इंजेक्शन

पहला चरण: DefaultTasksRepository में कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन का इस्तेमाल करना

  1. DefaultTaskRepository के कंस्ट्रक्टर को Application से बदलकर, डेटा सोर्स और कोराउटीन डिस्पैचर को इनपुट के तौर पर लेने के लिए बदलें. आपको अपनी जांचों के लिए भी इसे बदलना होगा. इसके बारे में, कोराउटीन के तीसरे सबक वाले सेक्शन में ज़्यादा जानकारी दी गई है.

DefaultTasksRepository.kt

// REPLACE
class DefaultTasksRepository private constructor(application: Application) { // Rest of class }

// WITH

class DefaultTasksRepository(
    private val tasksRemoteDataSource: TasksDataSource,
    private val tasksLocalDataSource: TasksDataSource,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) { // Rest of class }
  1. आपने डिपेंडेंसी पास कर दी हैं. इसलिए, init तरीके को हटाएं. अब आपको डिपेंडेंसी बनाने की ज़रूरत नहीं है.
  2. साथ ही, पुराने इंस्टेंस वैरिएबल भी मिटाएं. इन्हें कंस्ट्रक्टर में तय किया जा रहा है:

DefaultTasksRepository.kt

// Delete these old variables
private val tasksRemoteDataSource: TasksDataSource
private val tasksLocalDataSource: TasksDataSource
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
  1. आखिर में, नए कंस्ट्रक्टर का इस्तेमाल करने के लिए, getRepository तरीके को अपडेट करें:

DefaultTasksRepository.kt

    companion object {
        @Volatile
        private var INSTANCE: DefaultTasksRepository? = null

        fun getRepository(app: Application): DefaultTasksRepository {
            return INSTANCE ?: synchronized(this) {
                val database = Room.databaseBuilder(app,
                    ToDoDatabase::class.java, "Tasks.db")
                    .build()
                DefaultTasksRepository(TasksRemoteDataSource, TasksLocalDataSource(database.taskDao())).also {
                    INSTANCE = it
                }
            }
        }
    }

अब कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन का इस्तेमाल किया जा रहा है!

दूसरा चरण: अपनी जांचों में FakeDataSource का इस्तेमाल करना

अब आपका कोड कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन का इस्तेमाल कर रहा है. इसलिए, अपने DefaultTasksRepository की जांच करने के लिए, फ़र्ज़ी डेटा सोर्स का इस्तेमाल किया जा सकता है.

  1. DefaultTasksRepository क्लास के नाम पर राइट क्लिक करें और Generate को चुनें. इसके बाद, Test को चुनें.
  2. टेस्ट सोर्स सेट में DefaultTasksRepositoryTest बनाने के लिए, निर्देशों का पालन करें.
  3. अपनी नई DefaultTasksRepositoryTest क्लास में सबसे ऊपर, नीचे दी गई सदस्य वैरिएबल जोड़ें. इससे आपके फ़र्ज़ी डेटा सोर्स में मौजूद डेटा दिखेगा.

DefaultTasksRepositoryTest.kt

    private val task1 = Task("Title1", "Description1")
    private val task2 = Task("Title2", "Description2")
    private val task3 = Task("Title3", "Description3")
    private val remoteTasks = listOf(task1, task2).sortedBy { it.id }
    private val localTasks = listOf(task3).sortedBy { it.id }
    private val newTasks = listOf(task3).sortedBy { it.id }
  1. तीन वैरिएबल बनाएं. इनमें से दो FakeDataSource सदस्य वैरिएबल (आपकी रिपॉज़िटरी के हर डेटा सोर्स के लिए एक) और एक DefaultTasksRepository के लिए वैरिएबल बनाएं. आपको इस वैरिएबल की जांच करनी है.

DefaultTasksRepositoryTest.kt

    private lateinit var tasksRemoteDataSource: FakeDataSource
    private lateinit var tasksLocalDataSource: FakeDataSource

    // Class under test
    private lateinit var tasksRepository: DefaultTasksRepository

जांच किए जा सकने वाले DefaultTasksRepository को सेट अप और शुरू करने का तरीका बनाएं. यह DefaultTasksRepository, आपके टेस्ट डबल FakeDataSource का इस्तेमाल करेगा.

  1. createRepository नाम का एक तरीका बनाएं और उसे @Before से एनोटेट करें.
  2. remoteTasks और localTasks सूचियों का इस्तेमाल करके, अपने नकली डेटा सोर्स को इंस्टैंटिएट करें.
  3. अभी बनाए गए दो फ़र्ज़ी डेटा सोर्स और Dispatchers.Unconfined का इस्तेमाल करके, अपने tasksRepository को इंस्टैंशिएट करें.

फ़ाइनल तरीका, नीचे दिए गए कोड जैसा दिखना चाहिए.

DefaultTasksRepositoryTest.kt

    @Before
    fun createRepository() {
        tasksRemoteDataSource = FakeDataSource(remoteTasks.toMutableList())
        tasksLocalDataSource = FakeDataSource(localTasks.toMutableList())
        // Get a reference to the class under test
        tasksRepository = DefaultTasksRepository(
            // TODO Dispatchers.Unconfined should be replaced with Dispatchers.Main
            //  this requires understanding more about coroutines + testing
            //  so we will keep this as Unconfined for now.
            tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Unconfined
        )
    }

तीसरा चरण: DefaultTasksRepository getTasks() टेस्ट लिखना

अब DefaultTasksRepository टेस्ट लिखने का समय है!

  1. रिपॉज़िटरी के getTasks तरीके के लिए एक टेस्ट लिखें. जांच करें कि true के साथ getTasks को कॉल करने पर (इसका मतलब है कि इसे रिमोट डेटा सोर्स से फिर से लोड करना चाहिए), यह रिमोट डेटा सोर्स से डेटा दिखाता है, न कि लोकल डेटा सोर्स से.

DefaultTasksRepositoryTest.kt

@Test
    fun getTasks_requestsAllTasksFromRemoteDataSource(){
        // When tasks are requested from the tasks repository
        val tasks = tasksRepository.getTasks(true) as Success

        // Then tasks are loaded from the remote data source
        assertThat(tasks.data, IsEqual(remoteTasks))
    }

getTasks: को कॉल करने पर आपको गड़बड़ी का मैसेज मिलेगा

चौथा चरण: runBlockingTest जोड़ना

कोरूटीन में गड़बड़ी होने की वजह यह है कि getTasks एक suspend फ़ंक्शन है और इसे कॉल करने के लिए, आपको एक कोरूटीन लॉन्च करनी होगी. इसके लिए, आपको एक कोरूटीन स्कोप की ज़रूरत होती है. इस गड़बड़ी को ठीक करने के लिए, आपको अपनी जांचों में लॉन्चिंग कोरूटीन को मैनेज करने के लिए, कुछ gradle डिपेंडेंसी जोड़नी होंगी.

  1. testImplementation का इस्तेमाल करके, टेस्ट सोर्स सेट में को-रूटीन की जांच करने के लिए ज़रूरी डिपेंडेंसी जोड़ें.

app/build.gradle

testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"

सिंक करना न भूलें!

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

suspend फ़ंक्शन को कॉल करते समय, अपनी टेस्ट क्लास में runBlockingTest का इस्तेमाल करें. इस सीरीज़ के अगले कोडलैब में, आपको runBlockingTest के काम करने के तरीके और को-रूटीन की जांच करने के तरीके के बारे में ज़्यादा जानकारी मिलेगी.

  1. क्लास के ऊपर @ExperimentalCoroutinesApi जोड़ें. इससे पता चलता है कि आपको पता है कि क्लास में एक्सपेरिमेंट के तौर पर उपलब्ध को-रूटीन एपीआई (runBlockingTest) का इस्तेमाल किया जा रहा है. ऐसा न करने पर, आपको चेतावनी मिलेगी.
  2. अपने DefaultTasksRepositoryTest में वापस जाकर, runBlockingTest जोड़ें, ताकि यह आपके पूरे टेस्ट को कोड के "ब्लॉक" के तौर पर ले सके

फ़ाइनल टेस्ट, यहां दिए गए कोड की तरह दिखता है.

DefaultTasksRepositoryTest.kt

import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.core.IsEqual
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test


@ExperimentalCoroutinesApi
class DefaultTasksRepositoryTest {

    private val task1 = Task("Title1", "Description1")
    private val task2 = Task("Title2", "Description2")
    private val task3 = Task("Title3", "Description3")
    private val remoteTasks = listOf(task1, task2).sortedBy { it.id }
    private val localTasks = listOf(task3).sortedBy { it.id }
    private val newTasks = listOf(task3).sortedBy { it.id }

    private lateinit var tasksRemoteDataSource: FakeDataSource
    private lateinit var tasksLocalDataSource: FakeDataSource

    // Class under test
    private lateinit var tasksRepository: DefaultTasksRepository

    @Before
    fun createRepository() {
        tasksRemoteDataSource = FakeDataSource(remoteTasks.toMutableList())
        tasksLocalDataSource = FakeDataSource(localTasks.toMutableList())
        // Get a reference to the class under test
        tasksRepository = DefaultTasksRepository(
            // TODO Dispatchers.Unconfined should be replaced with Dispatchers.Main
            //  this requires understanding more about coroutines + testing
            //  so we will keep this as Unconfined for now.
            tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Unconfined
        )
    }

    @Test
    fun getTasks_requestsAllTasksFromRemoteDataSource() = runBlockingTest {
        // When tasks are requested from the tasks repository
        val tasks = tasksRepository.getTasks(true) as Success

        // Then tasks are loaded from the remote data source
        assertThat(tasks.data, IsEqual(remoteTasks))
    }

}
  1. getTasks_requestsAllTasksFromRemoteDataSource का नया टेस्ट चलाएं और पुष्टि करें कि यह काम कर रहा है और गड़बड़ी ठीक हो गई है!

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

यूनिट टेस्ट में, सिर्फ़ उस क्लास या तरीके की जांच की जानी चाहिए जिसमें आपकी दिलचस्पी है. इसे आइसोलेशन में टेस्टिंग कहा जाता है. इसमें, अपनी "यूनिट" को अलग किया जाता है और सिर्फ़ उस यूनिट के कोड की जांच की जाती है.

इसलिए, TasksViewModelTest को सिर्फ़ TasksViewModel कोड की जांच करनी चाहिए. इसे डेटाबेस, नेटवर्क या रिपॉज़िटरी क्लास में जांच नहीं करनी चाहिए. इसलिए, अपने व्यू मॉडल के लिए, ठीक उसी तरह एक फ़ेक रिपॉज़िटरी बनाएं जिस तरह आपने अभी-अभी अपनी रिपॉज़िटरी के लिए बनाई है. साथ ही, अपने टेस्ट में इसका इस्तेमाल करने के लिए, डिपेंडेंसी इंजेक्शन लागू करें.

इस टास्क में, व्यू मॉडल में डिपेंडेंसी इंजेक्शन लागू किया जाता है.

पहला चरण. TasksRepository इंटरफ़ेस बनाना

कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन का इस्तेमाल करने का पहला चरण, एक ऐसा सामान्य इंटरफ़ेस बनाना है जिसे फ़ेक और असली क्लास के बीच शेयर किया जाता है.

असल में यह कैसा दिखता है? TasksRemoteDataSource, TasksLocalDataSource, और FakeDataSource को देखें. ध्यान दें कि इन सभी का इंटरफ़ेस एक जैसा है: TasksDataSource. इससे DefaultTasksRepository के कंस्ट्रक्टर में यह बताया जा सकता है कि आपको TasksDataSource की ज़रूरत है.

DefaultTasksRepository.kt

class DefaultTasksRepository(
   private val tasksRemoteDataSource: TasksDataSource,
   private val tasksLocalDataSource: TasksDataSource,
   private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {

इसी की वजह से, हम आपके FakeDataSource को बदल पाते हैं!

इसके बाद, DefaultTasksRepository के लिए एक इंटरफ़ेस बनाएं. ठीक वैसे ही जैसे आपने डेटा सोर्स के लिए बनाया था. इसमें DefaultTasksRepository के सभी सार्वजनिक तरीके (सार्वजनिक एपीआई सरफेस) शामिल होने चाहिए.

  1. DefaultTasksRepository खोलें और क्लास के नाम पर राइट क्लिक करें. इसके बाद, Refactor -> Extract -> Interface चुनें.

  1. अलग फ़ाइल में निकालें चुनें.

  1. Extract Interface विंडो में, इंटरफ़ेस का नाम बदलकर TasksRepository करें.
  2. सदस्यों को फ़ॉर्म इंटरफ़ेस में शामिल करें सेक्शन में जाकर, कंपैनियन मोड का इस्तेमाल करने वाले दो सदस्यों और निजी तरीकों को छोड़कर, सभी सदस्यों को चुनें.


  1. कोड में बदलाव करें पर क्लिक करें. नया TasksRepository इंटरफ़ेस, data/source पैकेज में दिखना चाहिए.

अब DefaultTasksRepository, TasksRepository को लागू करता है.

  1. अपने ऐप्लिकेशन को चलाकर देखें (जांच नहीं), ताकि यह पक्का किया जा सके कि सब कुछ अब भी सही तरीके से काम कर रहा है.

दूसरा चरण. Create FakeTestRepository

अब आपके पास इंटरफ़ेस है. इसलिए, DefaultTaskRepository टेस्ट डबल बनाया जा सकता है.

  1. test सोर्स सेट में, data/source में Kotlin फ़ाइल और क्लास FakeTestRepository.kt बनाएं. साथ ही, इसे TasksRepository इंटरफ़ेस से बढ़ाएं.

FakeTestRepository.kt

class FakeTestRepository : TasksRepository  {
}

आपको इंटरफ़ेस के तरीकों को लागू करने के लिए कहा जाएगा.

  1. जब तक आपको सुझाव मेन्यू न दिख जाए, तब तक गड़बड़ी पर कर्सर घुमाएं. इसके बाद, सदस्यों को लागू करें पर क्लिक करके उसे चुनें.
  1. सभी तरीके चुनें और ठीक है दबाएं.

तीसरा चरण. FakeTestRepository के तरीकों को लागू करना

अब आपके पास FakeTestRepository क्लास है. इसमें "not implemented" तरीके शामिल हैं. FakeDataSource को लागू करने के तरीके की तरह ही, FakeTestRepository को डेटा स्ट्रक्चर की मदद से लागू किया जाएगा. इसके लिए, लोकल और रिमोट डेटा सोर्स के बीच जटिल मीडिएशन की ज़रूरत नहीं होगी.

ध्यान दें कि आपके FakeTestRepository को FakeDataSources या इस तरह की किसी भी चीज़ का इस्तेमाल करने की ज़रूरत नहीं है. इसे सिर्फ़ दिए गए इनपुट के आधार पर, असली जैसे दिखने वाले फ़र्ज़ी आउटपुट जनरेट करने होते हैं. टास्क की सूची को सेव करने के लिए, LinkedHashMap का इस्तेमाल किया जाएगा. साथ ही, ऑब्ज़र्वेबल टास्क के लिए MutableLiveData का इस्तेमाल किया जाएगा.

  1. FakeTestRepository में, LinkedHashMap वैरिएबल जोड़ें. यह वैरिएबल, टास्क की मौजूदा सूची को दिखाता है. साथ ही, MutableLiveData को अपने ऑब्ज़र्वेबल टास्क के लिए जोड़ें.

FakeTestRepository.kt

class FakeTestRepository : TasksRepository {

    var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()

    private val observableTasks = MutableLiveData<Result<List<Task>>>()


    // Rest of class
}

इन तरीकों को लागू करें:

  1. getTasks—इस तरीके में, tasksServiceData को tasksServiceData.values.toList() का इस्तेमाल करके सूची में बदलना चाहिए. इसके बाद, इसे Success नतीजे के तौर पर दिखाना चाहिए.
  2. refreshTasksobservableTasks की वैल्यू को getTasks() से मिली वैल्यू के तौर पर अपडेट करता है.
  3. observeTasksrunBlocking का इस्तेमाल करके कोरूटीन बनाता है और refreshTasks को चलाता है. इसके बाद, observableTasks दिखाता है.

उन तरीकों के लिए कोड यहां दिया गया है.

FakeTestRepository.kt

class FakeTestRepository : TasksRepository {

    var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()

    private val observableTasks = MutableLiveData<Result<List<Task>>>()

    override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
        return Result.Success(tasksServiceData.values.toList())
    }

    override suspend fun refreshTasks() {
        observableTasks.value = getTasks()
    }

    override fun observeTasks(): LiveData<Result<List<Task>>> {
        runBlocking { refreshTasks() }
        return observableTasks
    }

    // Rest of class

}

चरण 4. addTasks फ़ंक्शन की टेस्टिंग के लिए कोई तरीका जोड़ें

टेस्टिंग करते समय, यह बेहतर होता है कि आपकी रिपॉज़िटरी में कुछ Tasks पहले से मौजूद हों. saveTask को कई बार कॉल किया जा सकता है. हालांकि, इसे आसान बनाने के लिए, खास तौर पर टेस्ट के लिए एक हेल्पर मेथड जोड़ें. इससे आपको टास्क जोड़ने में मदद मिलेगी.

  1. addTasks तरीका जोड़ें. यह टास्क का vararg लेता है, हर टास्क को HashMap में जोड़ता है, और फिर टास्क को रीफ़्रेश करता है.

FakeTestRepository.kt

    fun addTasks(vararg tasks: Task) {
        for (task in tasks) {
            tasksServiceData[task.id] = task
        }
        runBlocking { refreshTasks() }
    }

इस समय, आपके पास टेस्टिंग के लिए एक फ़र्ज़ी रिपॉज़िटरी है. इसमें कुछ मुख्य तरीके लागू किए गए हैं. इसके बाद, इसे अपने टेस्ट में इस्तेमाल करें!

इस टास्क में, आपको ViewModel के अंदर फ़र्ज़ी क्लास का इस्तेमाल करना है. कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन का इस्तेमाल करें. इसके लिए, TasksViewModel के कंस्ट्रक्टर में TasksRepository वैरिएबल जोड़कर, कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन के ज़रिए दो डेटा सोर्स शामिल करें.

व्यू मॉडल के साथ यह प्रोसेस थोड़ी अलग होती है, क्योंकि इन्हें सीधे तौर पर नहीं बनाया जाता. उदाहरण के लिए:

class TasksFragment : Fragment() {

    private val viewModel by viewModels<TasksViewModel>()
    
    // Rest of class...

}


ऊपर दिए गए कोड में, viewModel's प्रॉपर्टी डेलिगेट का इस्तेमाल किया जा रहा है. यह व्यू मॉडल बनाता है. व्यू मॉडल बनाने के तरीके को बदलने के लिए, आपको ViewModelProvider.Factory को जोड़ना और उसका इस्तेमाल करना होगा. अगर आपको ViewModelProvider.Factory के बारे में नहीं पता है, तो इसके बारे में ज़्यादा जानने के लिए यहां जाएं.

पहला चरण. TasksViewModel में ViewModelFactory बनाना और उसका इस्तेमाल करना

सबसे पहले, Tasks स्क्रीन से जुड़ी क्लास और टेस्ट अपडेट करें.

  1. TasksViewModel खोलें.
  2. TasksViewModel के कंस्ट्रक्टर को बदलें, ताकि वह क्लास के अंदर कंस्ट्रक्ट करने के बजाय TasksRepository को इनपुट के तौर पर ले सके.

TasksViewModel.kt

// REPLACE
class TasksViewModel(application: Application) : AndroidViewModel(application) {

    private val tasksRepository = DefaultTasksRepository.getRepository(application)

    // Rest of class
}

// WITH

class TasksViewModel( private val tasksRepository: TasksRepository ) : ViewModel() { 
    // Rest of class 
}

कंस्ट्रक्टर बदलने की वजह से, अब आपको TasksViewModel बनाने के लिए फ़ैक्ट्री का इस्तेमाल करना होगा. फ़ैक्ट्री क्लास को TasksViewModel के साथ एक ही फ़ाइल में रखें. हालांकि, इसे अलग फ़ाइल में भी रखा जा सकता है.

  1. TasksViewModel फ़ाइल में सबसे नीचे, क्लास से बाहर, एक TasksViewModelFactory जोड़ें. यह एक सामान्य TasksRepository लेता है.

TasksViewModel.kt

@Suppress("UNCHECKED_CAST")
class TasksViewModelFactory (
    private val tasksRepository: TasksRepository
) : ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel> create(modelClass: Class<T>) =
        (TasksViewModel(tasksRepository) as T)
}


ViewModel बनाने के तरीके में बदलाव करने का यह स्टैंडर्ड तरीका है. अब आपके पास फ़ैक्ट्री है. इसका इस्तेमाल व्यू मॉडल बनाते समय करें.

  1. फ़ैक्ट्री का इस्तेमाल करने के लिए, TasksFragment को अपडेट करें.

TasksFragment.kt

// REPLACE
private val viewModel by viewModels<TasksViewModel>()

// WITH

private val viewModel by viewModels<TasksViewModel> {
    TasksViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}
  1. अपने ऐप्लिकेशन का कोड चलाएं और पक्का करें कि सब कुछ अब भी काम कर रहा हो!

दूसरा चरण. TasksViewModelTest में FakeTestRepository का इस्तेमाल करना

अब व्यू मॉडल टेस्ट में असली रिपॉज़िटरी का इस्तेमाल करने के बजाय, नकली रिपॉज़िटरी का इस्तेमाल किया जा सकता है.

  1. खोलें TasksViewModelTest.
  2. TasksViewModelTest में FakeTestRepository प्रॉपर्टी जोड़ें.

TaskViewModelTest.kt

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    // Use a fake repository to be injected into the viewmodel
    private lateinit var tasksRepository: FakeTestRepository
    
    // Rest of class
}
  1. तीन टास्क वाला FakeTestRepository बनाने के लिए, setupViewModel तरीके को अपडेट करें. इसके बाद, इस रिपॉज़िटरी की मदद से tasksViewModel बनाएं.

TasksViewModelTest.kt

    @Before
    fun setupViewModel() {
        // We initialise the tasks to 3, with one active and two completed
        tasksRepository = FakeTestRepository()
        val task1 = Task("Title1", "Description1")
        val task2 = Task("Title2", "Description2", true)
        val task3 = Task("Title3", "Description3", true)
        tasksRepository.addTasks(task1, task2, task3)

        tasksViewModel = TasksViewModel(tasksRepository)
        
    }
  1. AndroidX Test ApplicationProvider.getApplicationContext कोड का इस्तेमाल न करने पर, @RunWith(AndroidJUnit4::class) एनोटेशन को भी हटाया जा सकता है.
  2. जांच करें और पक्का करें कि वे अब भी काम कर रही हैं!

कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन का इस्तेमाल करके, आपने अब DefaultTasksRepository को डिपेंडेंसी के तौर पर हटा दिया है और टेस्ट में इसे FakeTestRepository से बदल दिया है.

तीसरा चरण. TaskDetail फ़्रैगमेंट और ViewModel को भी अपडेट करें

TaskDetailFragment और TaskDetailViewModel के लिए, बिलकुल एक जैसे बदलाव करें. इससे कोड को तैयार किया जाएगा, ताकि अगली बार TaskDetail टेस्ट लिखने में आसानी हो.

  1. TaskDetailViewModel खोलें.
  2. कंस्ट्रक्टर को अपडेट करें:

TaskDetailViewModel.kt

// REPLACE
class TaskDetailViewModel(application: Application) : AndroidViewModel(application) {

    private val tasksRepository = DefaultTasksRepository.getRepository(application)

    // Rest of class
}

// WITH

class TaskDetailViewModel(
    private val tasksRepository: TasksRepository
) : ViewModel() { // Rest of class }
  1. TaskDetailViewModel फ़ाइल में सबसे नीचे, क्लास के बाहर TaskDetailViewModelFactory जोड़ें.

TaskDetailViewModel.kt

@Suppress("UNCHECKED_CAST")
class TaskDetailViewModelFactory (
    private val tasksRepository: TasksRepository
) : ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel> create(modelClass: Class<T>) =
        (TaskDetailViewModel(tasksRepository) as T)
}
  1. फ़ैक्ट्री का इस्तेमाल करने के लिए, TasksFragment को अपडेट करें.

TasksFragment.kt

// REPLACE
private val viewModel by viewModels<TaskDetailViewModel>()

// WITH

private val viewModel by viewModels<TaskDetailViewModel> {
    TaskDetailViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}
  1. अपना कोड चलाएं और पक्का करें कि सब कुछ काम कर रहा है.

अब TasksFragment और TasksDetailFragment में, असली रिपॉज़िटरी की जगह FakeTestRepository का इस्तेमाल किया जा सकता है.

इसके बाद, आपको इंटिग्रेशन टेस्ट लिखने होंगे. इनसे आपके फ़्रैगमेंट और व्यू-मॉडल इंटरैक्शन की जांच की जा सकेगी. इससे आपको पता चलेगा कि आपका व्यू मॉडल कोड, यूज़र इंटरफ़ेस (यूआई) को सही तरीके से अपडेट करता है या नहीं. इसके लिए, आपको

  • ServiceLocator पैटर्न
  • Espresso और Mockito लाइब्रेरी

इंटिग्रेशन टेस्ट कई क्लास के इंटरैक्शन की जांच करते हैं. इससे यह पक्का किया जाता है कि एक साथ इस्तेमाल करने पर, वे उम्मीद के मुताबिक काम करें. इन टेस्ट को स्थानीय तौर पर (test सोर्स सेट) या इंस्ट्रुमेंटेशन टेस्ट (androidTest सोर्स सेट) के तौर पर चलाया जा सकता है.

इस मामले में, आपको हर फ़्रैगमेंट लेना होगा. साथ ही, फ़्रैगमेंट और व्यू मॉडल के लिए इंटिग्रेशन टेस्ट लिखने होंगे, ताकि फ़्रैगमेंट की मुख्य सुविधाओं की जांच की जा सके.

पहला चरण. Gradle डिपेंडेंसी जोड़ना

  1. यहां दी गई gradle डिपेंडेंसी जोड़ें.

app/build.gradle

    // Dependencies for Android instrumented unit tests
    androidTestImplementation "junit:junit:$junitVersion"
    androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"

    // Testing code should not be included in the main code.
    // Once https://issuetracker.google.com/128612536 is fixed this can be fixed.

    implementation "androidx.fragment:fragment-testing:$fragmentVersion"
    implementation "androidx.test:core:$androidXTestCoreVersion"

इन डिपेंडेंसी में ये शामिल हैं:

  • junit:junit—JUnit, जो बुनियादी टेस्ट स्टेटमेंट लिखने के लिए ज़रूरी है.
  • androidx.test:core—Core AndroidX टेस्ट लाइब्रेरी
  • kotlinx-coroutines-test—कोरूटीन टेस्टिंग लाइब्रेरी
  • androidx.fragment:fragment-testing—यह AndroidX टेस्ट लाइब्रेरी है. इसका इस्तेमाल, टेस्ट में फ़्रैगमेंट बनाने और उनकी स्थिति बदलने के लिए किया जाता है.

इन लाइब्रेरी का इस्तेमाल androidTest सोर्स सेट में किया जाएगा. इसलिए, इन्हें डिपेंडेंसी के तौर पर जोड़ने के लिए androidTestImplementation का इस्तेमाल करें.

दूसरा चरण. TaskDetailFragmentTest क्लास बनाएं

TaskDetailFragment में किसी एक टास्क के बारे में जानकारी होती है.

सबसे पहले, TaskDetailFragment के लिए फ़्रैगमेंट टेस्ट लिखें. ऐसा इसलिए, क्योंकि इसमें अन्य फ़्रैगमेंट की तुलना में काफ़ी बुनियादी सुविधाएं हैं.

  1. taskdetail.TaskDetailFragment खोलें.
  2. TaskDetailFragment के लिए, पहले की तरह ही एक टेस्ट जनरेट करें. डिफ़ॉल्ट विकल्पों को स्वीकार करें और इसे androidTest सोर्स सेट में रखें. इसे test सोर्स सेट में न रखें.

  1. TaskDetailFragmentTest क्लास में ये एनोटेशन जोड़ें.

TaskDetailFragmentTest.kt

@MediumTest
@RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest {

}

इन ऐनोटेशन का मकसद यह है:

  • @MediumTest—इस टेस्ट को "मीडियम रन-टाइम" इंटिग्रेशन टेस्ट के तौर पर मार्क करता है. यह @SmallTest यूनिट टेस्ट और @LargeTest बड़े एंड-टू-एंड टेस्ट से अलग होता है. इससे आपको ग्रुप बनाने और यह चुनने में मदद मिलती है कि किस साइज़ का टेस्ट चलाना है.
  • @RunWith(AndroidJUnit4::class)—इसका इस्तेमाल AndroidX Test का इस्तेमाल करने वाली किसी भी क्लास में किया जाता है.

तीसरा चरण. किसी टेस्ट से फ़्रैगमेंट लॉन्च करना

इस टास्क में, आपको AndroidX Testing library का इस्तेमाल करके TaskDetailFragment लॉन्च करना है. FragmentScenario, AndroidX Test की एक क्लास है. यह फ़्रैगमेंट के चारों ओर रैप करती है और आपको टेस्टिंग के लिए फ़्रैगमेंट के लाइफ़साइकल को सीधे तौर पर कंट्रोल करने की सुविधा देती है. फ़्रैगमेंट के लिए टेस्ट लिखने के लिए, आपको उस फ़्रैगमेंट के लिए FragmentScenario बनाना होगा जिसे टेस्ट किया जा रहा है (TaskDetailFragment).

  1. इस टेस्ट को TaskDetailFragmentTest में कॉपी करें.

TaskDetailFragmentTest.kt

    @Test
    fun activeTaskDetails_DisplayedInUi() {
        // GIVEN - Add active (incomplete) task to the DB
        val activeTask = Task("Active Task", "AndroidX Rocks", false)

        // WHEN - Details fragment launched to display task
        val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
        launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)

    }

ऊपर दिया गया यह कोड:

  • टास्क बनाता है.
  • यह एक Bundle बनाता है. यह उस टास्क के लिए फ़्रैगमेंट आर्ग्युमेंट दिखाता है जिसे फ़्रैगमेंट में पास किया जाता है.
  • launchFragmentInContainer फ़ंक्शन, इस बंडल और थीम के साथ FragmentScenario बनाता है.

यह टेस्ट अभी पूरा नहीं हुआ है, क्योंकि इसमें किसी भी चीज़ की पुष्टि नहीं की जा रही है. फ़िलहाल, टेस्ट चलाएं और देखें कि क्या होता है.

  1. यह एक इंस्ट्रूमेंटेड टेस्ट है. इसलिए, पक्का करें कि एम्युलेटर या आपका डिवाइस दिख रहा हो.
  2. टेस्ट को चलाएं.

कुछ चीज़ें होनी चाहिए.

  • पहला, यह एक इंस्ट्रुमेंटेड टेस्ट है. इसलिए, यह टेस्ट आपके फ़िज़िकल डिवाइस (अगर कनेक्ट किया गया है) या एम्युलेटर पर चलेगा.
  • इससे फ़्रैगमेंट लॉन्च होना चाहिए.
  • ध्यान दें कि यह किसी अन्य फ़्रैगमेंट पर नेविगेट नहीं करता है. साथ ही, इसमें ऐक्टिविटी से जुड़े कोई मेन्यू भी नहीं हैं. यह सिर्फ़ फ़्रैगमेंट है.

आखिर में, ध्यान से देखें और ध्यान दें कि फ़्रैगमेंट में "कोई डेटा नहीं" लिखा है, क्योंकि यह टास्क के डेटा को लोड नहीं कर पाता.

आपके टेस्ट को TaskDetailFragment लोड करना होगा (जो आपने कर लिया है). साथ ही, यह पुष्टि करनी होगी कि डेटा सही तरीके से लोड किया गया है. डेटा क्यों नहीं दिख रहा है? ऐसा इसलिए है, क्योंकि आपने कोई टास्क बनाया है, लेकिन उसे रिपॉज़िटरी में सेव नहीं किया है.

    @Test
    fun activeTaskDetails_DisplayedInUi() {
        // This DOES NOT save the task anywhere
        val activeTask = Task("Active Task", "AndroidX Rocks", false)

        val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
        launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)

    }

आपके पास यह FakeTestRepository है, लेकिन आपको अपने फ़्रैगमेंट के लिए, अपनी असली रिपॉज़िटरी को नकली रिपॉज़िटरी से बदलने का कोई तरीका चाहिए. अगला चरण यह है!

इस टास्क में, आपको ServiceLocator का इस्तेमाल करके, अपने फ़्रैगमेंट को नकली रिपॉज़िटरी देनी होगी. इससे आपको अपने फ़्रैगमेंट और व्यू मॉडल इंटिग्रेशन टेस्ट लिखने की अनुमति मिलेगी.

यहां कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन का इस्तेमाल नहीं किया जा सकता. पहले, व्यू मॉडल या रिपॉज़िटरी को डिपेंडेंसी देने के लिए इसका इस्तेमाल किया जाता था. कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन के लिए, क्लास को कंस्ट्रक्ट करना ज़रूरी है. फ़्रैगमेंट और गतिविधियां, ऐसी क्लास के उदाहरण हैं जिन्हें बनाया नहीं जाता. आम तौर पर, आपके पास इनके कंस्ट्रक्टर का ऐक्सेस भी नहीं होता.

आपने फ़्रैगमेंट नहीं बनाया है. इसलिए, कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन का इस्तेमाल करके, रिपॉज़िटरी टेस्ट डबल (FakeTestRepository) को फ़्रैगमेंट में नहीं बदला जा सकता. इसके बजाय, Service Locator पैटर्न का इस्तेमाल करें. सर्विस लोकेटर पैटर्न, डिपेंडेंसी इंजेक्शन का विकल्प है. इसमें "Service Locator" नाम की एक सिंगलटन क्लास बनाई जाती है. इसका मकसद, सामान्य और टेस्ट कोड, दोनों के लिए डिपेंडेंसी उपलब्ध कराना है. सामान्य ऐप्लिकेशन कोड (main सोर्स सेट) में, ये सभी डिपेंडेंसी सामान्य ऐप्लिकेशन डिपेंडेंसी होती हैं. जांच के लिए, सर्विस लोकेटर में बदलाव किया जाता है, ताकि डिपेंडेंसी के टेस्ट डबल वर्शन उपलब्ध कराए जा सकें.

Service Locator का इस्तेमाल नहीं किया जा रहा है


सर्विस लोकेटर का इस्तेमाल करना

इस कोडलैब ऐप्लिकेशन के लिए, यह तरीका अपनाएं:

  1. ऐसी सर्विस लोकेटर क्लास बनाएं जो रिपॉज़िटरी को बना और सेव कर सके. डिफ़ॉल्ट रूप से, यह एक "सामान्य" रिपॉज़िटरी बनाता है.
  2. अपने कोड को इस तरह से फिर से व्यवस्थित करें कि जब आपको किसी रिपॉज़िटरी की ज़रूरत हो, तब Service Locator का इस्तेमाल किया जा सके.
  3. जांच वाली क्लास में, सर्विस लोकेटर पर एक ऐसा तरीका कॉल करें जो "सामान्य" रिपॉज़िटरी को आपके टेस्ट डबल से बदल दे.

पहला चरण. ServiceLocator बनाएं

चलिए, एक ServiceLocator क्लास बनाते हैं. यह मुख्य सोर्स सेट में, ऐप्लिकेशन के बाकी कोड के साथ रहेगा. ऐसा इसलिए, क्योंकि इसका इस्तेमाल मुख्य ऐप्लिकेशन कोड करता है.

ध्यान दें: ServiceLocator एक सिंगलटन है. इसलिए, क्लास के लिए Kotlin object कीवर्ड का इस्तेमाल करें.

  1. मुख्य सोर्स सेट के टॉप लेवल में ServiceLocator.kt फ़ाइल बनाएं.
  2. ServiceLocator नाम का object तय करें.
  3. database और repository इंस्टेंस वैरिएबल बनाएं और दोनों को null पर सेट करें.
  4. रिपॉज़िटरी को @Volatile के साथ एनोटेट करें, क्योंकि इसका इस्तेमाल कई थ्रेड कर सकते हैं (@Volatile के बारे में ज़्यादा जानकारी यहां दी गई है).

आपका कोड, यहां दिखाए गए कोड की तरह दिखना चाहिए.

object ServiceLocator {

    private var database: ToDoDatabase? = null
    @Volatile
    var tasksRepository: TasksRepository? = null

}

फ़िलहाल, आपके ServiceLocator को सिर्फ़ यह पता होना चाहिए कि TasksRepository को वापस कैसे लाया जाए. यह पहले से मौजूद DefaultTasksRepository को वापस लाएगा या ज़रूरत पड़ने पर नया DefaultTasksRepository बनाएगा और उसे वापस लाएगा.

इन फ़ंक्शन के बारे में जानकारी दें:

  1. provideTasksRepository—यह पहले से मौजूद रिपॉज़िटरी उपलब्ध कराता है या नई रिपॉज़िटरी बनाता है. इस तरीके को synchronized पर this किया जाना चाहिए, ताकि एक साथ कई थ्रेड चलने की स्थितियों में, कभी भी गलती से दो रिपॉज़िटरी इंस्टेंस न बन जाएं.
  2. createTasksRepository—नई रिपॉज़िटरी बनाने का कोड. createTaskLocalDataSource को कॉल करेगा और एक नया TasksRemoteDataSource बनाएगा.
  3. createTaskLocalDataSource—नया लोकल डेटा सोर्स बनाने का कोड. createDataBase को कॉल किया जाएगा.
  4. createDataBase—नया डेटाबेस बनाने का कोड.

पूरा किया गया कोड यहां दिया गया है.

ServiceLocator.kt

object ServiceLocator {

    private var database: ToDoDatabase? = null
    @Volatile
    var tasksRepository: TasksRepository? = null

    fun provideTasksRepository(context: Context): TasksRepository {
        synchronized(this) {
            return tasksRepository ?: createTasksRepository(context)
        }
    }

    private fun createTasksRepository(context: Context): TasksRepository {
        val newRepo = DefaultTasksRepository(TasksRemoteDataSource, createTaskLocalDataSource(context))
        tasksRepository = newRepo
        return newRepo
    }

    private fun createTaskLocalDataSource(context: Context): TasksDataSource {
        val database = database ?: createDataBase(context)
        return TasksLocalDataSource(database.taskDao())
    }

    private fun createDataBase(context: Context): ToDoDatabase {
        val result = Room.databaseBuilder(
            context.applicationContext,
            ToDoDatabase::class.java, "Tasks.db"
        ).build()
        database = result
        return result
    }
}

दूसरा चरण. ऐप्लिकेशन में ServiceLocator का इस्तेमाल करना

आपको अपने मुख्य ऐप्लिकेशन कोड (टेस्ट नहीं) में बदलाव करना होगा, ताकि आप एक जगह पर रिपॉज़िटरी बना सकें. यह जगह आपकी ServiceLocator होगी.

यह ज़रूरी है कि आप रिपॉज़िटरी क्लास का सिर्फ़ एक इंस्टेंस बनाएं. इसके लिए, आपको मेरी ऐप्लिकेशन क्लास में सर्विस लोकेटर का इस्तेमाल करना होगा.

  1. अपने पैकेज के सबसे ऊपर के लेवल पर, TodoApplication खोलें. इसके बाद, अपनी रिपॉज़िटरी के लिए val बनाएं और उसे ServiceLocator.provideTaskRepository का इस्तेमाल करके हासिल की गई रिपॉज़िटरी असाइन करें.

TodoApplication.kt

class TodoApplication : Application() {

    val taskRepository: TasksRepository
        get() = ServiceLocator.provideTasksRepository(this)

    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) Timber.plant(DebugTree())
    }
}

ऐप्लिकेशन में रिपॉज़िटरी बनाने के बाद, अब DefaultTasksRepository में पुरानी getRepository विधि को हटाया जा सकता है.

  1. DefaultTasksRepository खोलें और कंपैनियन ऑब्जेक्ट मिटाएं.

DefaultTasksRepository.kt

// DELETE THIS COMPANION OBJECT
companion object {
    @Volatile
    private var INSTANCE: DefaultTasksRepository? = null

    fun getRepository(app: Application): DefaultTasksRepository {
        return INSTANCE ?: synchronized(this) {
            val database = Room.databaseBuilder(app,
                ToDoDatabase::class.java, "Tasks.db")
                .build()
            DefaultTasksRepository(TasksRemoteDataSource, TasksLocalDataSource(database.taskDao())).also {
                INSTANCE = it
            }
        }
    }
}

अब तक आपने जहां-जहां getRepository का इस्तेमाल किया है वहां-वहां ऐप्लिकेशन के taskRepository का इस्तेमाल करें. इससे यह पक्का होता है कि आपको सीधे तौर पर रिपॉज़िटरी बनाने के बजाय, ServiceLocator से मिली रिपॉज़िटरी मिल रही है.

  1. TaskDetailFragement खोलें और क्लास में सबसे ऊपर, getRepository पर कॉल करने का विकल्प ढूंढें.
  2. इस कॉल को ऐसे कॉल से बदलें जो TodoApplication से रिपॉज़िटरी हासिल करता है.

TaskDetailFragment.kt

// REPLACE this code
private val viewModel by viewModels<TaskDetailViewModel> {
    TaskDetailViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}

// WITH this code

private val viewModel by viewModels<TaskDetailViewModel> {
    TaskDetailViewModelFactory((requireContext().applicationContext as TodoApplication).taskRepository)
}
  1. TasksFragment के लिए भी ऐसा ही करें.

TasksFragment.kt

// REPLACE this code
    private val viewModel by viewModels<TasksViewModel> {
        TasksViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
    }


// WITH this code

    private val viewModel by viewModels<TasksViewModel> {
        TasksViewModelFactory((requireContext().applicationContext as TodoApplication).taskRepository)
    }
  1. StatisticsViewModel और AddEditTaskViewModel के लिए, रिपॉज़िटरी को ऐक्सेस करने वाले कोड को अपडेट करें, ताकि TodoApplication से रिपॉज़िटरी का इस्तेमाल किया जा सके.

TasksFragment.kt

// REPLACE this code
    private val tasksRepository = DefaultTasksRepository.getRepository(application)



// WITH this code

    private val tasksRepository = (application as TodoApplication).taskRepository

  1. अपना ऐप्लिकेशन चलाएं, न कि टेस्ट!

आपने सिर्फ़ कोड को फिर से व्यवस्थित किया है. इसलिए, ऐप्लिकेशन को बिना किसी समस्या के पहले की तरह काम करना चाहिए.

तीसरा चरण. FakeAndroidTestRepository बनाएं

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

  1. androidTest सोर्स सेट पर राइट क्लिक करें और डेटा पैकेज बनाएं. फिर से राइट क्लिक करें और सोर्स पैकेज बनाएं.
  2. इस सोर्स पैकेज में FakeAndroidTestRepository.kt नाम की एक नई क्लास बनाओ.
  3. नीचे दिए गए कोड को उस क्लास में कॉपी करें.

FakeAndroidTestRepository.kt

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
import com.example.android.architecture.blueprints.todoapp.data.Result
import com.example.android.architecture.blueprints.todoapp.data.Result.Error
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task
import kotlinx.coroutines.runBlocking
import java.util.LinkedHashMap



class FakeAndroidTestRepository : TasksRepository {

    var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()

    private var shouldReturnError = false

    private val observableTasks = MutableLiveData<Result<List<Task>>>()

    fun setReturnError(value: Boolean) {
        shouldReturnError = value
    }

    override suspend fun refreshTasks() {
        observableTasks.value = getTasks()
    }

    override suspend fun refreshTask(taskId: String) {
        refreshTasks()
    }

    override fun observeTasks(): LiveData<Result<List<Task>>> {
        runBlocking { refreshTasks() }
        return observableTasks
    }

    override fun observeTask(taskId: String): LiveData<Result<Task>> {
        runBlocking { refreshTasks() }
        return observableTasks.map { tasks ->
            when (tasks) {
                is Result.Loading -> Result.Loading
                is Error -> Error(tasks.exception)
                is Success -> {
                    val task = tasks.data.firstOrNull() { it.id == taskId }
                        ?: return@map Error(Exception("Not found"))
                    Success(task)
                }
            }
        }
    }

    override suspend fun getTask(taskId: String, forceUpdate: Boolean): Result<Task> {
        if (shouldReturnError) {
            return Error(Exception("Test exception"))
        }
        tasksServiceData[taskId]?.let {
            return Success(it)
        }
        return Error(Exception("Could not find task"))
    }

    override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
        if (shouldReturnError) {
            return Error(Exception("Test exception"))
        }
        return Success(tasksServiceData.values.toList())
    }

    override suspend fun saveTask(task: Task) {
        tasksServiceData[task.id] = task
    }

    override suspend fun completeTask(task: Task) {
        val completedTask = Task(task.title, task.description, true, task.id)
        tasksServiceData[task.id] = completedTask
    }

    override suspend fun completeTask(taskId: String) {
        // Not required for the remote data source.
        throw NotImplementedError()
    }

    override suspend fun activateTask(task: Task) {
        val activeTask = Task(task.title, task.description, false, task.id)
        tasksServiceData[task.id] = activeTask
    }

    override suspend fun activateTask(taskId: String) {
        throw NotImplementedError()
    }

    override suspend fun clearCompletedTasks() {
        tasksServiceData = tasksServiceData.filterValues {
            !it.isCompleted
        } as LinkedHashMap<String, Task>
    }

    override suspend fun deleteTask(taskId: String) {
        tasksServiceData.remove(taskId)
        refreshTasks()
    }

    override suspend fun deleteAllTasks() {
        tasksServiceData.clear()
        refreshTasks()
    }

   
    fun addTasks(vararg tasks: Task) {
        for (task in tasks) {
            tasksServiceData[task.id] = task
        }
        runBlocking { refreshTasks() }
    }
}

चरण 4. टेस्ट के लिए ServiceLocator तैयार करना

ठीक है, अब टेस्टिंग के दौरान टेस्ट डबल का इस्तेमाल करने के लिए ServiceLocator का इस्तेमाल करें. इसके लिए, आपको अपने ServiceLocator कोड में कुछ कोड जोड़ना होगा.

  1. ServiceLocator.kt खोलें.
  2. tasksRepository के लिए सेटर को @VisibleForTesting के तौर पर मार्क करें. इस एनोटेशन से यह पता चलता है कि सेटर को सार्वजनिक तौर पर इसलिए उपलब्ध कराया गया है, ताकि उसकी जांच की जा सके.

ServiceLocator.kt

    @Volatile
    var tasksRepository: TasksRepository? = null
        @VisibleForTesting set

चाहे आपने टेस्ट अकेले किया हो या टेस्ट के ग्रुप में, आपके टेस्ट एक जैसे होने चाहिए. इसका मतलब है कि आपके टेस्ट में ऐसा कोई व्यवहार नहीं होना चाहिए जो एक-दूसरे पर निर्भर हो. इसका मतलब है कि टेस्ट के बीच ऑब्जेक्ट शेयर करने से बचना चाहिए.

ServiceLocator एक सिंगलटन है. इसलिए, इसे गलती से टेस्ट के बीच शेयर किया जा सकता है. इससे बचने के लिए, एक ऐसा तरीका बनाएं जो टेस्ट के बीच ServiceLocator की स्थिति को सही तरीके से रीसेट करे.

  1. Any वैल्यू के साथ lock नाम का एक इंस्टेंस वैरिएबल जोड़ें.

ServiceLocator.kt

private val lock = Any()
  1. जांच के लिए खास तौर पर तैयार किया गया resetRepository नाम का एक तरीका जोड़ें. यह डेटाबेस को मिटा देता है और रिपॉज़िटरी और डेटाबेस, दोनों को शून्य पर सेट कर देता है.

ServiceLocator.kt

    @VisibleForTesting
    fun resetRepository() {
        synchronized(lock) {
            runBlocking {
                TasksRemoteDataSource.deleteAllTasks()
            }
            // Clear all data to avoid test pollution.
            database?.apply {
                clearAllTables()
                close()
            }
            database = null
            tasksRepository = null
        }
    }

चरण 5. ServiceLocator का इस्तेमाल करना

इस चरण में, ServiceLocator का इस्तेमाल किया जाता है.

  1. TaskDetailFragmentTest खोलें.
  2. lateinit TasksRepository वैरिएबल का एलान करें.
  3. हर टेस्ट से पहले FakeAndroidTestRepository को सेट अप करने और हर टेस्ट के बाद उसे हटाने के लिए, सेटअप और टीयर डाउन का तरीका जोड़ें.

TaskDetailFragmentTest.kt

    private lateinit var repository: TasksRepository

    @Before
    fun initRepository() {
        repository = FakeAndroidTestRepository()
        ServiceLocator.tasksRepository = repository
    }

    @After
    fun cleanupDb() = runBlockingTest {
        ServiceLocator.resetRepository()
    }
  1. activeTaskDetails_DisplayedInUi() के फ़ंक्शन बॉडी को runBlockingTest में रैप करें.
  2. फ़्रैगमेंट लॉन्च करने से पहले, activeTask को रिपॉज़िटरी में सेव करें.
repository.saveTask(activeTask)

फ़ाइनल टेस्ट, यहां दिए गए कोड की तरह दिखता है.

TaskDetailFragmentTest.kt

    @Test
    fun activeTaskDetails_DisplayedInUi()  = runBlockingTest{
        // GIVEN - Add active (incomplete) task to the DB
        val activeTask = Task("Active Task", "AndroidX Rocks", false)
        repository.saveTask(activeTask)

        // WHEN - Details fragment launched to display task
        val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
        launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)

    }
  1. @ExperimentalCoroutinesApi का इस्तेमाल करके, पूरी क्लास के लिए एनोटेशन जोड़ें.

कोड पूरा होने के बाद, ऐसा दिखेगा.

TaskDetailFragmentTest.kt

@MediumTest
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest {

    private lateinit var repository: TasksRepository

    @Before
    fun initRepository() {
        repository = FakeAndroidTestRepository()
        ServiceLocator.tasksRepository = repository
    }

    @After
    fun cleanupDb() = runBlockingTest {
        ServiceLocator.resetRepository()
    }


    @Test
    fun activeTaskDetails_DisplayedInUi()  = runBlockingTest{
        // GIVEN - Add active (incomplete) task to the DB
        val activeTask = Task("Active Task", "AndroidX Rocks", false)
        repository.saveTask(activeTask)

        // WHEN - Details fragment launched to display task
        val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
        launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)

    }

}
  1. activeTaskDetails_DisplayedInUi() टेस्ट चलाएं.

पहले की तरह, आपको फ़्रैगमेंट दिखेगा. हालांकि, इस बार आपने रिपॉज़िटरी को सही तरीके से सेट अप किया है. इसलिए, अब इसमें टास्क की जानकारी दिखेगी.


इस चरण में, आपको Espresso यूज़र इंटरफ़ेस (यूआई) टेस्टिंग लाइब्रेरी का इस्तेमाल करके, इंटिग्रेशन टेस्ट पूरा करना होगा. आपने अपने कोड को इस तरह से स्ट्रक्चर किया है कि यूज़र इंटरफ़ेस (यूआई) के लिए, दावे के साथ टेस्ट जोड़े जा सकें. इसके लिए, आपको Espresso testing library का इस्तेमाल करना होगा.

Espresso की मदद से ये काम किए जा सकते हैं:

  • व्यू के साथ इंटरैक्ट करना. जैसे, बटन पर क्लिक करना, बार को स्लाइड करना या स्क्रीन को नीचे की ओर स्क्रोल करना.
  • यह दावा करना कि कुछ व्यू स्क्रीन पर हैं या किसी खास स्थिति में हैं. जैसे, उनमें कोई खास टेक्स्ट शामिल है या कोई चेकबॉक्स चुना गया है वगैरह.

पहला चरण. Gradle डिपेंडेंसी के बारे में जानकारी

आपके पास पहले से ही मुख्य Espresso डिपेंडेंसी होगी, क्योंकि यह Android प्रोजेक्ट में डिफ़ॉल्ट रूप से शामिल होती है.

app/build.gradle

dependencies {

  // ALREADY in your code
    androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
   
 // Other dependencies
}

androidx.test.espresso:espresso-core—यह मुख्य Espresso डिपेंडेंसी है. नया Android प्रोजेक्ट बनाते समय, यह डिफ़ॉल्ट रूप से शामिल होती है. इसमें ज़्यादातर व्यू और उन पर की गई कार्रवाइयों के लिए, बुनियादी टेस्टिंग कोड होता है.

दूसरा चरण. ऐनिमेशन बंद करना

Espresso टेस्ट, असली डिवाइस पर चलते हैं. इसलिए, ये इंस्ट्रुमेंटेशन टेस्ट होते हैं. एक समस्या ऐनिमेशन से जुड़ी है: अगर कोई ऐनिमेशन लैग करता है और आपको यह जांच करनी है कि कोई व्यू स्क्रीन पर है या नहीं, लेकिन वह अब भी ऐनिमेट हो रहा है, तो Espresso गलती से टेस्ट को फ़ेल कर सकता है. इससे Espresso टेस्ट में गड़बड़ियां हो सकती हैं.

Espresso की मदद से यूज़र इंटरफ़ेस (यूआई) की टेस्टिंग के लिए, ऐनिमेशन बंद करना सबसे सही तरीका है. इससे टेस्ट भी तेज़ी से पूरा होगा!

  1. टेस्टिंग डिवाइस पर, सेटिंग > डेवलपर के लिए सेटिंग और टूल पर जाएं.
  2. इन तीन सेटिंग को बंद करें: विंडो ऐनिमेशन स्केल, ट्रांज़िशन ऐनिमेशन स्केल, और ऐनिमेटर अवधि स्केल.

तीसरा चरण. Espresso टेस्ट देखना

Espresso टेस्ट लिखने से पहले, Espresso के कुछ कोड देखें.

onView(withId(R.id.task_detail_complete_checkbox)).perform(click()).check(matches(isChecked()))

यह स्टेटमेंट, task_detail_complete_checkbox आईडी वाले चेकबॉक्स व्यू को ढूंढता है. इसके बाद, उस पर क्लिक करता है. इसके बाद, यह पुष्टि करता है कि वह चेक किया गया है.

Espresso के ज़्यादातर स्टेटमेंट में चार हिस्से होते हैं:

1. स्टैटिक एस्प्रेसो का तरीका

onView

onView, स्टैटिक Espresso तरीके का एक उदाहरण है. यह Espresso स्टेटमेंट शुरू करता है. onView सबसे ज़्यादा इस्तेमाल किया जाने वाला विकल्प है. हालांकि, onData जैसे अन्य विकल्प भी उपलब्ध हैं.

2. ViewMatcher

withId(R.id.task_detail_title_text)

withId, ViewMatcher का एक उदाहरण है. यह आईडी के हिसाब से व्यू दिखाता है. दस्तावेज़ में जाकर, व्यू मैच करने वाले अन्य टूल के बारे में जानकारी पाएं.

3. ViewAction

perform(click())

perform तरीका, जो ViewAction लेता है. ViewAction एक ऐसी कार्रवाई होती है जो व्यू पर की जा सकती है. उदाहरण के लिए, यहां व्यू पर क्लिक करना.

4. ViewAssertion

check(matches(isChecked()))

check, जिसमें ViewAssertion लगता है. ViewAssertions, व्यू के बारे में कुछ जानकारी देते हैं या पुष्टि करते हैं. आम तौर पर, matches असर्शन का इस्तेमाल किया जाता है.ViewAssertion असर्शन को पूरा करने के लिए, किसी दूसरे ViewMatcher का इस्तेमाल करें. इस मामले में, isChecked का इस्तेमाल करें.

ध्यान दें कि Espresso स्टेटमेंट में, perform और check, दोनों को हमेशा कॉल नहीं किया जाता. आपके पास ऐसे स्टेटमेंट हो सकते हैं जो सिर्फ़ check का इस्तेमाल करके पुष्टि करते हैं या सिर्फ़ perform का इस्तेमाल करके ViewAction करते हैं.

  1. TaskDetailFragmentTest.kt खोलें.
  2. activeTaskDetails_DisplayedInUi टेस्ट को अपडेट करें.

TaskDetailFragmentTest.kt

    @Test
    fun activeTaskDetails_DisplayedInUi() = runBlockingTest{
        // GIVEN - Add active (incomplete) task to the DB
        val activeTask = Task("Active Task", "AndroidX Rocks", false)
        repository.saveTask(activeTask)

        // WHEN - Details fragment launched to display task
        val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
        launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)

        // THEN - Task details are displayed on the screen
        // make sure that the title/description are both shown and correct
        onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
        onView(withId(R.id.task_detail_title_text)).check(matches(withText("Active Task")))
        onView(withId(R.id.task_detail_description_text)).check(matches(isDisplayed()))
        onView(withId(R.id.task_detail_description_text)).check(matches(withText("AndroidX Rocks")))
        // and make sure the "active" checkbox is shown unchecked
        onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isDisplayed()))
        onView(withId(R.id.task_detail_complete_checkbox)).check(matches(not(isChecked())))
    }

ज़रूरत पड़ने पर, यहां इंपोर्ट स्टेटमेंट दिए गए हैं:

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import org.hamcrest.core.IsNot.not
  1. // THEN टिप्पणी के बाद का सारा कॉन्टेंट, Espresso का इस्तेमाल करता है. टेस्ट के स्ट्रक्चर और withId के इस्तेमाल की जांच करें. साथ ही, इस बात की पुष्टि करें कि जानकारी वाला पेज कैसा दिखना चाहिए.
  2. टेस्ट को चलाएं और पुष्टि करें कि यह पास हो गया है.

चरण 4. ज़रूरी नहीं, अपना Espresso टेस्ट लिखें

अब खुद एक टेस्ट लिखें.

  1. completedTaskDetails_DisplayedInUi नाम का एक नया टेस्ट बनाएं और इस स्केलेटन कोड को कॉपी करें.

TaskDetailFragmentTest.kt

    @Test
    fun completedTaskDetails_DisplayedInUi() = runBlockingTest{
        // GIVEN - Add completed task to the DB
       
        // WHEN - Details fragment launched to display task
        
        // THEN - Task details are displayed on the screen
        // make sure that the title/description are both shown and correct
}
  1. पिछले टेस्ट को देखकर, इस टेस्ट को पूरा करें.
  2. चलाएं पर क्लिक करें और पुष्टि करें कि टेस्ट पास हो गया है.

तैयार किया गया completedTaskDetails_DisplayedInUi, इस कोड जैसा दिखना चाहिए.

TaskDetailFragmentTest.kt

    @Test
    fun completedTaskDetails_DisplayedInUi() = runBlockingTest{
        // GIVEN - Add completed task to the DB
        val completedTask = Task("Completed Task", "AndroidX Rocks", true)
        repository.saveTask(completedTask)

        // WHEN - Details fragment launched to display task
        val bundle = TaskDetailFragmentArgs(completedTask.id).toBundle()
        launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)

        // THEN - Task details are displayed on the screen
        // make sure that the title/description are both shown and correct
        onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
        onView(withId(R.id.task_detail_title_text)).check(matches(withText("Completed Task")))
        onView(withId(R.id.task_detail_description_text)).check(matches(isDisplayed()))
        onView(withId(R.id.task_detail_description_text)).check(matches(withText("AndroidX Rocks")))
        // and make sure the "active" checkbox is shown unchecked
        onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isDisplayed()))
        onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isChecked()))
    }

इस आखिरी चरण में, आपको नेविगेशन कॉम्पोनेंट की जांच करने का तरीका बताया जाएगा. इसके लिए, मॉक नाम के टेस्ट डबल और Mockito टेस्टिंग लाइब्रेरी का इस्तेमाल किया जाएगा.

इस कोडलैब में, आपने टेस्ट डबल का इस्तेमाल किया है. इसे फ़ेक कहा जाता है. फ़ेक, टेस्ट डबल के कई टाइप में से एक है. Navigation component की टेस्टिंग के लिए, आपको किस टेस्ट डबल का इस्तेमाल करना चाहिए?

सोचें कि नेविगेशन कैसे काम करता है. टास्क की जानकारी वाली स्क्रीन पर जाने के लिए, TasksFragment में मौजूद किसी टास्क को दबाने की कल्पना करें.

यहां TasksFragment में दिया गया कोड, दबाए जाने पर टास्क की जानकारी वाली स्क्रीन पर ले जाता है.

TasksFragment.kt

private fun openTaskDetails(taskId: String) {
    val action = TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment(taskId)
    findNavController().navigate(action)
}


नेविगेशन, navigate तरीके को कॉल करने की वजह से होता है. अगर आपको assert स्टेटमेंट लिखना है, तो यह जांचने का कोई आसान तरीका नहीं है कि आपने TaskDetailFragment पर नेविगेट किया है या नहीं. नेविगेट करना एक मुश्किल कार्रवाई है. इससे TaskDetailFragment को शुरू करने के अलावा, कोई साफ़ आउटपुट नहीं मिलता या स्थिति में बदलाव नहीं होता.

यह दावा किया जा सकता है कि navigate तरीके को सही ऐक्शन पैरामीटर के साथ कॉल किया गया था. मॉक टेस्ट डबल ठीक यही काम करता है. यह जाँच करता है कि क्या कुछ खास तरीकों को कॉल किया गया था.

Mockito, टेस्ट डबल बनाने के लिए एक फ़्रेमवर्क है. एपीआई और नाम में मॉक शब्द का इस्तेमाल किया गया है, लेकिन इसका मतलब सिर्फ़ मॉक बनाना नहीं है. यह स्टब और स्पाय भी बना सकता है.

आपको Mockito का इस्तेमाल करके एक मॉक NavigationController बनाना होगा. इससे यह पुष्टि की जा सकती है कि नेविगेट करने के तरीके को सही तरीके से कॉल किया गया था.

पहला चरण. Gradle डिपेंडेंसी जोड़ना

  1. gradle डिपेंडेंसी जोड़ें.

app/build.gradle

    // Dependencies for Android instrumented unit tests
    androidTestImplementation "org.mockito:mockito-core:$mockitoVersion"

    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:$dexMakerVersion" 

    androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"



  • org.mockito:mockito-core—यह Mockito डिपेंडेंसी है.
  • dexmaker-mockito—Android प्रोजेक्ट में Mockito का इस्तेमाल करने के लिए, इस लाइब्रेरी की ज़रूरत होती है. Mockito को रनटाइम पर क्लास जनरेट करने की ज़रूरत होती है. Android पर, यह काम डेक्स बाइट कोड का इस्तेमाल करके किया जाता है. इसलिए, यह लाइब्रेरी Mockito को Android पर रनटाइम के दौरान ऑब्जेक्ट जनरेट करने की सुविधा देती है.
  • androidx.test.espresso:espresso-contrib—यह लाइब्रेरी, बाहरी योगदानों से बनी है. इसलिए, इसका नाम भी ऐसा ही है. इसमें DatePicker और RecyclerView जैसे ज़्यादा बेहतर व्यू के लिए टेस्टिंग कोड शामिल होता है. इसमें सुलभता से जुड़ी जांचें और CountingIdlingResource नाम की क्लास भी शामिल है, जिसके बारे में बाद में बताया गया है.

दूसरा चरण. Create TasksFragmentTest

  1. TasksFragment खोलें.
  2. TasksFragment क्लास के नाम पर राइट क्लिक करें. इसके बाद, जनरेट करें और फिर जांच करें को चुनें. androidTest सोर्स सेट में कोई टेस्ट बनाएं.
  3. इस कोड को TasksFragmentTest में कॉपी करें.

TasksFragmentTest.kt

@RunWith(AndroidJUnit4::class)
@MediumTest
@ExperimentalCoroutinesApi
class TasksFragmentTest {

    private lateinit var repository: TasksRepository

    @Before
    fun initRepository() {
        repository = FakeAndroidTestRepository()
        ServiceLocator.tasksRepository = repository
    }

    @After
    fun cleanupDb() = runBlockingTest {
        ServiceLocator.resetRepository()
    }

}

यह कोड, आपके लिखे गए TaskDetailFragmentTest कोड से मिलता-जुलता है. यह FakeAndroidTestRepository को सेट अप और बंद करता है. नेविगेशन टेस्ट जोड़ें. इससे यह जांच की जा सकेगी कि टास्क की सूची में मौजूद किसी टास्क पर क्लिक करने से, आपको सही TaskDetailFragment पर ले जाया जाता है या नहीं.

  1. टेस्ट clickTask_navigateToDetailFragmentOne जोड़ें.

TasksFragmentTest.kt

    @Test
    fun clickTask_navigateToDetailFragmentOne() = runBlockingTest {
        repository.saveTask(Task("TITLE1", "DESCRIPTION1", false, "id1"))
        repository.saveTask(Task("TITLE2", "DESCRIPTION2", true, "id2"))

        // GIVEN - On the home screen
        val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)
        
    }
  1. मॉक बनाने के लिए, Mockito के mock फ़ंक्शन का इस्तेमाल करें.

TasksFragmentTest.kt

 val navController = mock(NavController::class.java)

Mockito में मॉक करने के लिए, उस क्लास को पास करें जिसे आपको मॉक करना है.

इसके बाद, आपको अपने NavController को फ़्रैगमेंट से जोड़ना होगा. onFragment की मदद से, फ़्रैगमेंट पर ही तरीकों को कॉल किया जा सकता है.

  1. अपने नए मॉक को फ़्रैगमेंट का NavController बनाएं.
scenario.onFragment {
    Navigation.setViewNavController(it.view!!, navController)
}
  1. RecyclerView में मौजूद "TITLE1" टेक्स्ट वाले आइटम पर क्लिक करने के लिए कोड जोड़ें.
// WHEN - Click on the first list item
        onView(withId(R.id.tasks_list))
            .perform(RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
                hasDescendant(withText("TITLE1")), click()))

RecyclerViewActions, espresso-contrib लाइब्रेरी का हिस्सा है. इसकी मदद से, RecyclerView पर Espresso ऐक्शन किए जा सकते हैं.

  1. पुष्टि करें कि navigate को सही तर्क के साथ कॉल किया गया था.
// THEN - Verify that we navigate to the first detail screen
verify(navController).navigate(
    TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment( "id1")

Mockito का verify तरीका, इसे मॉक बनाता है. इसकी मदद से, यह पुष्टि की जा सकती है कि मॉक किए गए navController ने किसी खास तरीके (navigate) को पैरामीटर (actionTasksFragmentToTaskDetailFragment, "id1" आईडी के साथ) के साथ कॉल किया है.

पूरी जांच कुछ ऐसी दिखती है:

@Test
fun clickTask_navigateToDetailFragmentOne() = runBlockingTest {
    repository.saveTask(Task("TITLE1", "DESCRIPTION1", false, "id1"))
    repository.saveTask(Task("TITLE2", "DESCRIPTION2", true, "id2"))

    // GIVEN - On the home screen
    val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)
    
                val navController = mock(NavController::class.java)
    scenario.onFragment {
        Navigation.setViewNavController(it.view!!, navController)
    }

    // WHEN - Click on the first list item
    onView(withId(R.id.tasks_list))
        .perform(RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
            hasDescendant(withText("TITLE1")), click()))


    // THEN - Verify that we navigate to the first detail screen
    verify(navController).navigate(
        TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment( "id1")
    )
}
  1. टेस्ट चलाएं!

संक्षेप में, नेविगेशन की जांच करने के लिए, ये काम किए जा सकते हैं:

  1. NavController मॉक बनाने के लिए, Mockito का इस्तेमाल करें.
  2. उस मॉक किए गए NavController को फ़्रैगमेंट से अटैच करें.
  3. पुष्टि करें कि नेविगेट करने के लिए सही कार्रवाई और पैरामीटर इस्तेमाल किए गए हों.

तीसरा चरण. ज़रूरी नहीं, clickAddTaskButton_navigateToAddEditFragment लिखें

यह देखने के लिए कि क्या नेविगेशन टेस्ट खुद लिखा जा सकता है, यह टास्क आज़माएं.

  1. ऐसा टेस्ट clickAddTaskButton_navigateToAddEditFragment लिखें जिससे यह पता चले कि + FAB पर क्लिक करने से, AddEditTaskFragment पर रीडायरेक्ट किया जाता है.

इसका जवाब यहां दिया गया है.

TasksFragmentTest.kt

    @Test
    fun clickAddTaskButton_navigateToAddEditFragment() {
        // GIVEN - On the home screen
        val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)
        val navController = mock(NavController::class.java)
        scenario.onFragment {
            Navigation.setViewNavController(it.view!!, navController)
        }

        // WHEN - Click on the "+" button
        onView(withId(R.id.add_task_fab)).perform(click())

        // THEN - Verify that we navigate to the add screen
        verify(navController).navigate(
            TasksFragmentDirections.actionTasksFragmentToAddEditTaskFragment(
                null, getApplicationContext<Context>().getString(R.string.add_task)
            )
        )
    }

आपने जो कोड शुरू किया था और फ़ाइनल कोड के बीच का अंतर देखने के लिए, यहां क्लिक करें.

पूरे कोडलैब का कोड डाउनलोड करने के लिए, यहां दिए गए git कमांड का इस्तेमाल करें:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout end_codelab_2


इसके अलावा, रिपॉज़िटरी को Zip फ़ाइल के तौर पर डाउनलोड किया जा सकता है. इसके बाद, इसे अनज़िप करके Android Studio में खोला जा सकता है.

ज़िप फ़ाइल डाउनलोड करें

इस कोडलैब में, मैन्युअल डिपेंडेंसी इंजेक्शन और सर्विस लोकेटर को सेट अप करने का तरीका बताया गया है. साथ ही, Android Kotlin ऐप्लिकेशन में फ़ेक और मॉक का इस्तेमाल करने का तरीका भी बताया गया है. खास तौर पर:

  • आपको किस चीज़ की जांच करनी है और आपकी जांच की रणनीति क्या है, इससे यह तय होता है कि आपको अपने ऐप्लिकेशन के लिए किस तरह के टेस्ट लागू करने हैं. यूनिट टेस्ट, फ़ोकस किए गए और तेज़ होते हैं. इंटिग्रेशन टेस्ट से, आपके प्रोग्राम के अलग-अलग हिस्सों के बीच इंटरैक्शन की पुष्टि की जाती है. एंड-टू-एंड टेस्ट से सुविधाओं की पुष्टि की जाती है. ये टेस्ट सबसे सटीक होते हैं. इन्हें अक्सर इंस्ट्रुमेंट किया जाता है और इन्हें पूरा होने में ज़्यादा समय लग सकता है.
  • आपके ऐप्लिकेशन के आर्किटेक्चर से यह तय होता है कि उसकी टेस्टिंग कितनी मुश्किल है.
  • टीडीडी या टेस्ट ड्रिवन डेवलपमेंट एक ऐसी रणनीति है जिसमें सबसे पहले टेस्ट लिखे जाते हैं. इसके बाद, टेस्ट पास करने के लिए सुविधा बनाई जाती है.
  • टेस्टिंग के लिए अपने ऐप्लिकेशन के कुछ हिस्सों को अलग करने के लिए, टेस्ट डबल का इस्तेमाल किया जा सकता है. टेस्ट डबल, क्लास का एक ऐसा वर्शन होता है जिसे खास तौर पर टेस्टिंग के लिए बनाया जाता है. उदाहरण के लिए, डेटाबेस या इंटरनेट से डेटा पाने का झूठा दावा करना.
  • किसी असली क्लास को टेस्टिंग क्लास से बदलने के लिए, डिपेंडेंसी इंजेक्शन का इस्तेमाल करें. उदाहरण के लिए, कोई रिपॉज़िटरी या नेटवर्किंग लेयर.
  • यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट लॉन्च करने के लिए, instrumented testing (androidTest) का इस्तेमाल करें.
  • जब कंस्ट्रक्टर डिपेंडेंसी इंजेक्शन का इस्तेमाल नहीं किया जा सकता, तब अक्सर सर्विस लोकेटर का इस्तेमाल किया जा सकता है. उदाहरण के लिए, फ़्रैगमेंट लॉन्च करने के लिए. सर्विस लोकेटर पैटर्न, डिपेंडेंसी इंजेक्शन का विकल्प है. इसमें "Service Locator" नाम की एक सिंगलटन क्लास बनाई जाती है. इसका मकसद, सामान्य और टेस्ट कोड, दोनों के लिए डिपेंडेंसी उपलब्ध कराना है.

Udacity का कोर्स:

Android डेवलपर का दस्तावेज़:

वीडियो:

अन्य:

इस कोर्स में मौजूद अन्य कोडलैब के लिंक के लिए, Advanced Android in Kotlin कोडलैब का लैंडिंग पेज देखें.