paint-brush
.NET C# में टाइमर का उपयोग करने का सबसे अच्छा तरीकाद्वारा@ahmedtarekhasan
4,081 रीडिंग
4,081 रीडिंग

.NET C# में टाइमर का उपयोग करने का सबसे अच्छा तरीका

द्वारा Ahmed Tarek Hasan30m2023/03/29
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

अपने सी # एप्लिकेशन में System.Timers.Timer का उपयोग करके, आपको इसे सारणित करने और यूनिट टेस्ट के साथ अपने मॉड्यूल को कवर करने में सक्षम होने में समस्याएं आ सकती हैं। इस लेख में, हम इन चुनौतियों पर विजय पाने के सर्वोत्तम तरीकों पर चर्चा करेंगे। अंत तक, आप अपने मॉड्यूल का 100% कवरेज प्राप्त करने में सक्षम होंगे।
featured image - .NET C# में टाइमर का उपयोग करने का सबसे अच्छा तरीका
Ahmed Tarek Hasan HackerNoon profile picture

टाइमर पर पूरा नियंत्रण कैसे रखें और यूनिट टेस्ट के साथ 100% कवरेज तक कैसे पहुंचें

अपने .NET C# एप्लिकेशन में System.Timers.Timer का उपयोग करते समय, आपको इसे अमूर्त करने और यूनिट टेस्ट के साथ अपने मॉड्यूल को कवर करने में सक्षम होने में समस्या का सामना करना पड़ सकता है।


इस लेख में, हम इन चुनौतियों पर विजय पाने के सर्वोत्तम तरीकों पर चर्चा करेंगे, और अंत तक, आप अपने मॉड्यूल का 100% कवरेज हासिल करने में सक्षम होंगे।


अनस्प्लैश पर लीना ट्रोचेज़ द्वारा फोटो

दृष्टिकोण

हम अपने समाधान के लिए इस तरह से संपर्क करने जा रहे हैं:


  1. काम करने के लिए एक बहुत ही सरल उदाहरण के साथ आओ।


  2. सरल खराब समाधान से प्रारंभ करें।


  3. जब तक हम अंतिम प्रारूप तक नहीं पहुंच जाते, तब तक इसे बढ़ाने का प्रयास करते रहें।


  4. हमारी यात्रा के माध्यम से सीखे गए पाठों को सारांशित करना।


अनस्प्लैश पर जेम्स हैरिसन द्वारा फोटो

उदाहरण

हमारे उदाहरण में, हम एक साधारण कंसोल एप्लिकेशन का निर्माण करेंगे जो केवल एक साधारण काम करेगा: कंसोल को दिनांक और समय प्रति सेकंड लिखने के लिए System.Timers.Timer का उपयोग करें


अंत में, आपको इसके साथ समाप्त होना चाहिए:


छवि अहमद तारेक द्वारा


जैसा कि आप देख सकते हैं, आवश्यकताओं के मामले में यह सरल है, कुछ भी फैंसी नहीं है।


अनस्प्लैश पर मिकेल सीजेन द्वारा फोटो, अहमद तारेक द्वारा समायोजित

अस्वीकरण

  1. इस आलेख में लक्षित अन्य सर्वोत्तम प्रथाओं पर मुख्य ध्यान केंद्रित करने के लिए कुछ सर्वोत्तम प्रथाओं को अनदेखा/छोड़ दिया जाएगा।


  2. इस लेख में, हम इकाई परीक्षणों के साथ System.Timers.Timer का उपयोग करके मॉड्यूल को कवर करने पर ध्यान केंद्रित करेंगे। हालाँकि, शेष समाधान इकाई परीक्षणों में शामिल नहीं होंगे। यदि आप इसके बारे में अधिक जानना चाहते हैं, तो आप लेख को देख सकते हैं यूनिट टेस्ट के साथ .NET C# कंसोल एप्लिकेशन को पूरी तरह से कैसे कवर करें .


  3. कुछ तृतीय-पक्ष पुस्तकालय हैं जिनका उपयोग लगभग समान परिणाम प्राप्त करने के लिए किया जा सकता है। हालाँकि, जब भी संभव हो, मैं एक पूरी बड़ी तृतीय-पक्ष लाइब्रेरी पर निर्भर रहने के बजाय एक मूल सरल डिज़ाइन का अनुसरण करूँगा।


अनस्प्लैश पर मारिया टेनेवा द्वारा फोटो, अहमद तारेक द्वारा समायोजित

खराब समाधान

इस समाधान में, हम अमूर्तता की एक परत प्रदान किए बिना सीधे System.Timers.Timer का उपयोग करेंगे।


समाधान की संरचना इस तरह दिखनी चाहिए:


छवि अहमद तारेक द्वारा


यह केवल एक कंसोल TimerApp प्रोजेक्ट के साथ एक यूजिंगटाइमर समाधान है।


मैंने जानबूझकर System.Console अमूर्त करने में कुछ समय और प्रयास का निवेश किया है ताकि यह साबित किया IConsole सके कि यह टाइमर के साथ हमारी समस्या का समाधान नहीं करेगा।


 namespace TimerApp.Abstractions { public interface IConsole { void WriteLine(object? value); } }


हमें अपने उदाहरण में केवल System.Console.WriteLine उपयोग करने की आवश्यकता होगी; इसलिए यह एकमात्र अमूर्त विधि है।


 namespace TimerApp.Abstractions { public interface IPublisher { void StartPublishing(); void StopPublishing(); } }


IPublisher इंटरफ़ेस पर हमारे पास केवल दो विधियाँ हैं: StartPublishing और StopPublishing


अब, कार्यान्वयन के लिए:


 using TimerApp.Abstractions; namespace TimerApp.Implementations { public class Console : IConsole { public void WriteLine(object? value) { System.Console.WriteLine(value); } } }


Console System.Console के लिए केवल एक पतला आवरण है।


 using System.Timers; using TimerApp.Abstractions; namespace TimerApp.Implementations { public class Publisher : IPublisher { private readonly Timer m_Timer; private readonly IConsole m_Console; public Publisher(IConsole console) { m_Timer = new Timer(); m_Timer.Enabled = true; m_Timer.Interval = 1000; m_Timer.Elapsed += Handler; m_Console = console; } public void StartPublishing() { m_Timer.Start(); } public void StopPublishing() { m_Timer.Stop(); } private void Handler(object sender, ElapsedEventArgs args) { m_Console.WriteLine(args.SignalTime); } } }


Publisher IPublisher का एक सरल कार्यान्वयन है। यह एक System.Timers.Timer उपयोग कर रहा है और बस इसे कॉन्फ़िगर कर रहा है।


इसमें IConsole को एक निर्भरता के रूप में परिभाषित किया गया है। यह मेरे दृष्टिकोण से सर्वोत्तम अभ्यास नहीं है। यदि आप समझना चाहते हैं कि मेरा क्या मतलब है, तो आप लेख की जांच कर सकते हैं .NET C# में DI, IoC और IoC कंटेनरों का उपयोग कब नहीं करना चाहिए .


हालाँकि, केवल सादगी के लिए, हम इसे कंस्ट्रक्टर में निर्भरता के रूप में इंजेक्ट करेंगे।


हम टाइमर अंतराल को 1000 मिलीसेकंड (1 सेकंड) पर भी सेट कर रहे हैं, और हैंडलर को टाइमर SignalTime कंसोल पर लिखने के लिए सेट कर रहे हैं।


 using TimerApp.Abstractions; using TimerApp.Implementations; namespace TimerApp { public class Program { static void Main(string[] args) { IPublisher publisher = new Publisher(new Implementations.Console()); publisher.StartPublishing(); System.Console.ReadLine(); publisher.StopPublishing(); } } }


यहाँ, Program क्लास में, हम ज्यादा कुछ नहीं कर रहे हैं। हम केवल Publisher वर्ग का उदाहरण बना रहे हैं और प्रकाशन शुरू कर रहे हैं।


इसे चलाने से कुछ इस तरह समाप्त होना चाहिए:


छवि अहमद तारेक द्वारा


अब, प्रश्न यह है कि यदि आप Publisher वर्ग के लिए इकाई परीक्षण लिखने जा रहे हैं, तो आप क्या कर सकते हैं?


दुर्भाग्य से, उत्तर होगा: बहुत ज्यादा नहीं


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


दूसरा, मान लीजिए कि हमने कोड को संशोधित किया ताकि टाइमर अब कन्स्ट्रक्टर में इंजेक्शन दिया जा सके; फिर भी, सवाल यह होगा कि यूनिट टेस्ट कैसे लिखा जाए और टाइमर को मॉक या स्टब से कैसे बदला जाए?


मैंने किसी को चिल्लाते हुए सुना, "आइए टाइमर को अमूर्त में लपेटें और टाइमर के बजाय इसे इंजेक्ट करें।"


हां, यह सही है, हालांकि यह इतना आसान नहीं है। कुछ तरकीबें हैं जो मैं अगले भाग में समझाने जा रहा हूँ।


अनस्प्लैश पर कार्सन मास्टर्सन द्वारा फोटो, अहमद तारेक द्वारा समायोजित

अच्छा समाधान

यह समय अच्छे समाधान का है। आइए देखें कि हम इसके बारे में क्या कर सकते हैं।


समाधान की संरचना इस तरह दिखनी चाहिए:


छवि अहमद तारेक द्वारा


नए कंसोल बेटरटाइमर ऐप प्रोजेक्ट के साथ यह वही यूजिंगटाइमर समाधान है।


IConsole , IPublisher , और Console समान होंगे।

आईटाइमर

 using System; namespace BetterTimerApp.Abstractions { public delegate void TimerIntervalElapsedEventHandler(object sender, DateTime dateTime); public interface ITimer : IDisposable { event TimerIntervalElapsedEventHandler TimerIntervalElapsed; bool Enabled { get; set; } double Interval { get; set; } void Start(); void Stop(); } }


हम यहाँ क्या देख सकते हैं:


  1. हमने नए प्रतिनिधि TimerIntervalElapsedEventHandler परिभाषित किया। यह प्रतिनिधि हमारे ITimer द्वारा उठाई जाने वाली घटना का प्रतिनिधित्व करता है।


  2. आप तर्क दे सकते हैं कि हमें इस नए प्रतिनिधि की आवश्यकता नहीं है क्योंकि हमारे पास पहले से ही देशी ElapsedEventHandler है जो पहले से ही System.Timers.Timer द्वारा उपयोग किया जाता है।


  3. हा ये तो है। हालांकि, आप देखेंगे कि ElapsedEventHandler ईवेंट ElapsedEventArgs ईवेंट तर्कों के रूप में प्रदान कर रहा है। इस ElapsedEventArgs में एक निजी कन्स्ट्रक्टर है, और आप अपना खुद का उदाहरण नहीं बना पाएंगे। इसके अतिरिक्त, ElapsedEventArgs क्लास में परिभाषित SignalTime प्रॉपर्टी केवल पढ़ने के लिए है। इसलिए, आप इसे चाइल्ड क्लास में ओवरराइड नहीं कर पाएंगे।


  4. इस वर्ग को अद्यतन करने के लिए Microsoft के लिए एक परिवर्तन अनुरोध टिकट खोला गया है, लेकिन इस लेख को लिखे जाने तक, कोई परिवर्तन लागू नहीं किया गया था।


  5. साथ ही, ध्यान दें कि ITimer IDisposable बढ़ाता है।



प्रकाशक

 using System; using BetterTimerApp.Abstractions; namespace BetterTimerApp.Implementations { public class Publisher : IPublisher { private readonly ITimer m_Timer; private readonly IConsole m_Console; public Publisher(ITimer timer, IConsole console) { m_Timer = timer; m_Timer.Enabled = true; m_Timer.Interval = 1000; m_Timer.TimerIntervalElapsed += Handler; m_Console = console; } public void StartPublishing() { m_Timer.Start(); } public void StopPublishing() { m_Timer.Stop(); } private void Handler(object sender, DateTime dateTime) { m_Console.WriteLine(dateTime); } } }


छोटे बदलावों को छोड़कर यह लगभग पुराने Publisher जैसा ही है। अब, हमारे पास ITimer एक निर्भरता के रूप में परिभाषित किया गया है जिसे कंस्ट्रक्टर के माध्यम से इंजेक्ट किया गया है। बाकी कोड वही होगा।

घड़ी

 using System; using System.Collections.Generic; using System.Linq; using System.Timers; using BetterTimerApp.Abstractions; namespace BetterTimerApp.Implementations { public class Timer : ITimer { private Dictionary<TimerIntervalElapsedEventHandler, List<ElapsedEventHandler>> m_Handlers = new(); private bool m_IsDisposed; private System.Timers.Timer m_Timer; public Timer() { m_Timer = new System.Timers.Timer(); } public event TimerIntervalElapsedEventHandler TimerIntervalElapsed { add { var internalHandler = (ElapsedEventHandler)((sender, args) => { value.Invoke(sender, args.SignalTime); }); if (!m_Handlers.ContainsKey(value)) { m_Handlers.Add(value, new List<ElapsedEventHandler>()); } m_Handlers[value].Add(internalHandler); m_Timer.Elapsed += internalHandler; } remove { m_Timer.Elapsed -= m_Handlers[value].Last(); m_Handlers[value].RemoveAt(m_Handlers[value].Count - 1); if (!m_Handlers[value].Any()) { m_Handlers.Remove(value); } } } public bool Enabled { get => m_Timer.Enabled; set => m_Timer.Enabled = value; } public double Interval { get => m_Timer.Interval; set => m_Timer.Interval = value; } public void Start() { m_Timer.Start(); } public void Stop() { m_Timer.Stop(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing && m_Handlers.Any()) { foreach (var internalHandlers in m_Handlers.Values) { if (internalHandlers?.Any() ?? false) { internalHandlers.ForEach(handler => m_Timer.Elapsed -= handler); } } m_Timer.Dispose(); m_Timer = null; m_Handlers.Clear(); m_Handlers = null; } m_IsDisposed = true; } ~Timer() { Dispose(false); } } }


यहीं पर लगभग सारा जादू होता है।


हम यहाँ क्या देख सकते हैं:


  1. आंतरिक रूप से, हम System.Timers.Timer उपयोग कर रहे हैं।


  2. हमने आईडीस्पोजेबल डिजाइन पैटर्न लागू किया। यही कारण है कि आप private bool m_IsDisposed , public void Dispose() , protected virtual void Dispose(bool disposing) , और ~Timer() देख सकते हैं।


  3. निर्माता में, हम System.Timers.Timer का एक नया उदाहरण प्रारंभ कर रहे हैं। हम इसे बाकी चरणों में आंतरिक टाइमर के रूप में संदर्भित करेंगे।


  4. public bool Enabled , public double Interval , public void Start() , और public void Stop() के लिए, हम केवल आंतरिक टाइमर के कार्यान्वयन को सौंप रहे हैं।


  5. public event TimerIntervalElapsedEventHandler TimerIntervalElapsed के लिए, यह सबसे महत्वपूर्ण हिस्सा है; तो चलिए इसका स्टेप बाय स्टेप विश्लेषण करते हैं।


  6. इस घटना के साथ हमें क्या करना है जब कोई बाहर से इसकी सदस्यता/सदस्यता समाप्त करता है। इस मामले में, हम इसे आंतरिक टाइमर पर प्रतिबिंबित करना चाहते हैं।


  7. दूसरे शब्दों में, अगर किसी बाहरी व्यक्ति के पास हमारे ITimer का उदाहरण है, तो उसे कुछ ऐसा करने में सक्षम होना t.TimerIntervalElapsed += (sender, dateTime) => { //do something }


  8. इस समय, हमें क्या करना चाहिए आंतरिक रूप से m_Timer.Elapsed += (sender, elapsedEventArgs) => { //do something } जैसा कुछ करना चाहिए।


  9. हालाँकि, हमें यह ध्यान रखने की आवश्यकता है कि दो हैंडलर समान नहीं हैं क्योंकि वे वास्तव में विभिन्न प्रकार के हैं; TimerIntervalElapsedEventHandler और ElapsedEventHandler


  10. इसलिए, हमें आने वाले TimerIntervalElapsedEventHandler को एक नए आंतरिक ElapsedEventHandler में लपेटने की आवश्यकता है। यह कुछ ऐसा है जो हम कर सकते हैं।


  11. हालाँकि, हमें यह भी ध्यान रखना होगा कि किसी समय किसी को TimerIntervalElapsedEventHandler ईवेंट से हैंडलर की सदस्यता समाप्त करने की आवश्यकता हो सकती है।


  12. इसका मतलब यह है कि, इस समय, हमें यह जानने में सक्षम होना चाहिए कि कौन सा ElapsedEventHandler हैंडलर उस TimerIntervalElapsedEventHandler हैंडलर से मेल खाता है ताकि हम इसे आंतरिक टाइमर से अनसब्सक्राइब कर सकें।


  13. इसे प्राप्त करने का एकमात्र तरीका एक शब्दकोश में प्रत्येक TimerIntervalElapsedEventHandler हैंडलर और नव निर्मित ElapsedEventHandler हैंडलर का ट्रैक रखना है। इस तरह, TimerIntervalElapsedEventHandler हैंडलर में पारित होने के बारे में जानकर, हम संबंधित ElapsedEventHandler हैंडलर को जान सकते हैं।


  14. हालाँकि, हमें यह भी ध्यान रखना होगा कि बाहर से कोई व्यक्ति एक ही TimerIntervalElapsedEventHandler हैंडलर को एक से अधिक बार सब्सक्राइब कर सकता है।


  15. हां, यह तार्किक नहीं है, लेकिन फिर भी, यह करने योग्य है। इसलिए, पूर्णता के लिए, प्रत्येक TimerIntervalElapsedEventHandler हैंडलर के लिए, हम ElapsedEventHandler हैंडलर की एक सूची रखेंगे।


  16. ज्यादातर मामलों में, इस सूची में केवल एक प्रविष्टि होगी जब तक कि डुप्लिकेट सदस्यता के मामले में न हो।


  17. और इसीलिए आप यह देख सकते हैं private Dictionary<TimerIntervalElapsedEventHandler, List<ElapsedEventHandler>> m_Handlers = new(); .


 public event TimerIntervalElapsedEventHandler TimerIntervalElapsed { add { var internalHandler = (ElapsedEventHandler)((sender, args) => { value.Invoke(sender, args.SignalTime); }); if (!m_Handlers.ContainsKey(value)) { m_Handlers.Add(value, new List<ElapsedEventHandler>()); } m_Handlers[value].Add(internalHandler); m_Timer.Elapsed += internalHandler; } remove { m_Timer.Elapsed -= m_Handlers[value].Last(); m_Handlers[value].RemoveAt(m_Handlers[value].Count - 1); if (!m_Handlers[value].Any()) { m_Handlers.Remove(value); } } }


add में, हम एक नया ElapsedEventHandler बना रहे हैं, m_Handlers में एक रिकॉर्ड जोड़कर इसे TimerIntervalElapsedEventHandler पर मैप कर रहे हैं, और अंत में आंतरिक टाइमर की सदस्यता ले रहे हैं।


remove में, हम ElapsedEventHandler हैंडलर की संबंधित सूची प्राप्त कर रहे हैं, अंतिम हैंडलर का चयन कर रहे हैं, इसे आंतरिक टाइमर से सदस्यता समाप्त कर रहे हैं, इसे सूची से हटा रहे हैं, और सूची खाली होने पर पूरी प्रविष्टि को हटा रहे हैं।


उल्लेखनीय भी है, Dispose कार्यान्वयन।


 protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing && m_Handlers.Any()) { foreach (var internalHandlers in m_Handlers.Values) { if (internalHandlers?.Any() ?? false) { internalHandlers.ForEach(handler => m_Timer.Elapsed -= handler); } } m_Timer.Dispose(); m_Timer = null; m_Handlers.Clear(); m_Handlers = null; } m_IsDisposed = true; }


हम आंतरिक टाइमर से शेष सभी हैंडलर की सदस्यता समाप्त कर रहे हैं, आंतरिक टाइमर का निपटान कर रहे हैं, और m_Handlers शब्दकोश को साफ़ कर रहे हैं।

कार्यक्रम

 using BetterTimerApp.Abstractions; using BetterTimerApp.Implementations; namespace BetterTimerApp { public class Program { static void Main(string[] args) { var timer = new Timer(); IPublisher publisher = new Publisher(timer, new Implementations.Console()); publisher.StartPublishing(); System.Console.ReadLine(); publisher.StopPublishing(); timer.Dispose(); } } }


यहां हम अभी भी बहुत कुछ नहीं कर रहे हैं। यह लगभग पुराने समाधान जैसा ही है।


इसे चलाने से कुछ इस तरह समाप्त होना चाहिए:


छवि अहमद तारेक द्वारा


अनस्प्लैश पर Testalize.me द्वारा फोटो, अहमद तारेक द्वारा समायोजित

परीक्षण का समय, सत्य का क्षण

अब, हमारे पास अपना अंतिम डिज़ाइन है। हालाँकि, हमें यह देखने की आवश्यकता है कि क्या यह डिज़ाइन वास्तव में हमारे Publisher मॉड्यूल को इकाई परीक्षणों के साथ कवर करने में हमारी मदद कर सकता है।


समाधान की संरचना इस तरह दिखनी चाहिए:


छवि अहमद तारेक द्वारा


मैं परीक्षण के लिए NUnit और Moq का उपयोग कर रहा हूं। आप निश्चित रूप से अपने पसंदीदा पुस्तकालयों के साथ काम कर सकते हैं।

टाइमरस्टब

 using System; using System.Collections.Generic; using BetterTimerApp.Abstractions; namespace BetterTimerApp.Tests.Stubs { public enum Action { Start = 1, Stop = 2, Triggered = 3, Enabled = 4, Disabled = 5, IntervalSet = 6 } public class ActionLog { public Action Action { get; } public string Message { get; } public ActionLog(Action action, string message) { Action = action; Message = message; } } public class TimerStub : ITimer { private bool m_Enabled; private double m_Interval; public event TimerIntervalElapsedEventHandler TimerIntervalElapsed; public Dictionary<int, ActionLog> Log = new(); public bool Enabled { get => m_Enabled; set { m_Enabled = value; Log.Add(Log.Count + 1, new ActionLog(value ? Action.Enabled : Action.Disabled, value ? "Enabled" : "Disabled")); } } public double Interval { get => m_Interval; set { m_Interval = value; Log.Add(Log.Count + 1, new ActionLog(Action.IntervalSet, m_Interval.ToString("G17"))); } } public void Start() { Log.Add(Log.Count + 1, new ActionLog(Action.Start, "Started")); } public void Stop() { Log.Add(Log.Count + 1, new ActionLog(Action.Stop, "Stopped")); } public void TriggerTimerIntervalElapsed(DateTime dateTime) { OnTimerIntervalElapsed(dateTime); Log.Add(Log.Count + 1, new ActionLog(Action.Triggered, "Triggered")); } protected void OnTimerIntervalElapsed(DateTime dateTime) { TimerIntervalElapsed?.Invoke(this, dateTime); } public void Dispose() { Log.Clear(); Log = null; } } }


हम यहाँ क्या देख सकते हैं:


  1. हमने अपने टाइमर स्टब के माध्यम से किए गए कार्यों को लॉग करते समय उपयोग किए जाने वाले Action एनम को परिभाषित किया। इसका उपयोग बाद में की गई आंतरिक क्रियाओं पर जोर देने के लिए किया जाएगा।


  2. साथ ही, हमने लॉगिंग के लिए उपयोग की जाने वाली ActionLog क्लास को परिभाषित किया।


  3. हमने TimerStub क्लास को ITimer के स्टब के रूप में परिभाषित किया। Publisher मॉड्यूल का परीक्षण करते समय हम बाद में इस स्टब का उपयोग करेंगे।


  4. कार्यान्वयन सरल है। यह उल्लेख करने योग्य है कि हमने एक अतिरिक्त public void TriggerTimerIntervalElapsed(DateTime dateTime) विधि जोड़ी है ताकि हम इकाई परीक्षण के भीतर मैन्युअल रूप से स्टब को ट्रिगर कर सकें।


  5. हम dateTime के अपेक्षित मूल्य में भी पास कर सकते हैं ताकि हमारे पास दावा करने के लिए ज्ञात मूल्य हो।

प्रकाशक परीक्षण

 using System; using BetterTimerApp.Abstractions; using BetterTimerApp.Implementations; using BetterTimerApp.Tests.Stubs; using Moq; using NUnit.Framework; using Action = BetterTimerApp.Tests.Stubs.Action; namespace BetterTimerApp.Tests.Tests { [TestFixture] public class PublisherTests { private TimerStub m_TimerStub; private Mock<IConsole> m_ConsoleMock; private Publisher m_Sut; [SetUp] public void SetUp() { m_TimerStub = new TimerStub(); m_ConsoleMock = new Mock<IConsole>(); m_Sut = new Publisher(m_TimerStub, m_ConsoleMock.Object); } [TearDown] public void TearDown() { m_Sut = null; m_ConsoleMock = null; m_TimerStub = null; } [Test] public void ConstructorTest() { Assert.AreEqual(Action.Enabled, m_TimerStub.Log[1].Action); Assert.AreEqual(Action.Enabled.ToString(), m_TimerStub.Log[1].Message); Assert.AreEqual(Action.IntervalSet, m_TimerStub.Log[2].Action); Assert.AreEqual(1000.ToString("G17"), m_TimerStub.Log[2].Message); } [Test] public void StartPublishingTest() { // Arrange var expectedDateTime = DateTime.Now; m_ConsoleMock .Setup ( m => m.WriteLine ( It.Is<DateTime>(p => p == expectedDateTime) ) ) .Verifiable(); // Act m_Sut.StartPublishing(); m_TimerStub.TriggerTimerIntervalElapsed(expectedDateTime); // Assert ConstructorTest(); m_ConsoleMock .Verify ( m => m.WriteLine(expectedDateTime) ); Assert.AreEqual(Action.Start, m_TimerStub.Log[3].Action); Assert.AreEqual("Started", m_TimerStub.Log[3].Message); Assert.AreEqual(Action.Triggered, m_TimerStub.Log[4].Action); Assert.AreEqual(Action.Triggered.ToString(), m_TimerStub.Log[4].Message); } [Test] public void StopPublishingTest() { // Act m_Sut.StopPublishing(); // Assert ConstructorTest(); Assert.AreEqual(Action.Stop, m_TimerStub.Log[3].Action); Assert.AreEqual("Stopped", m_TimerStub.Log[3].Message); } [Test] public void FullProcessTest() { // Arrange var expectedDateTime1 = DateTime.Now; var expectedDateTime2 = expectedDateTime1 + TimeSpan.FromSeconds(1); var expectedDateTime3 = expectedDateTime2 + TimeSpan.FromSeconds(1); var sequence = new MockSequence(); m_ConsoleMock .InSequence(sequence) .Setup ( m => m.WriteLine ( It.Is<DateTime>(p => p == expectedDateTime1) ) ) .Verifiable(); m_ConsoleMock .InSequence(sequence) .Setup ( m => m.WriteLine ( It.Is<DateTime>(p => p == expectedDateTime2) ) ) .Verifiable(); m_ConsoleMock .InSequence(sequence) .Setup ( m => m.WriteLine ( It.Is<DateTime>(p => p == expectedDateTime3) ) ) .Verifiable(); // Act m_Sut.StartPublishing(); m_TimerStub.TriggerTimerIntervalElapsed(expectedDateTime1); // Assert ConstructorTest(); m_ConsoleMock .Verify ( m => m.WriteLine(expectedDateTime1) ); Assert.AreEqual(Action.Start, m_TimerStub.Log[3].Action); Assert.AreEqual("Started", m_TimerStub.Log[3].Message); Assert.AreEqual(Action.Triggered, m_TimerStub.Log[4].Action); Assert.AreEqual(Action.Triggered.ToString(), m_TimerStub.Log[4].Message); // Act m_TimerStub.TriggerTimerIntervalElapsed(expectedDateTime2); // Assert m_ConsoleMock .Verify ( m => m.WriteLine(expectedDateTime2) ); Assert.AreEqual(Action.Triggered, m_TimerStub.Log[5].Action); Assert.AreEqual(Action.Triggered.ToString(), m_TimerStub.Log[5].Message); // Act m_Sut.StopPublishing(); // Assert Assert.AreEqual(Action.Stop, m_TimerStub.Log[6].Action); Assert.AreEqual("Stopped", m_TimerStub.Log[6].Message); } } }


अब, जैसा कि आप देख सकते हैं, हमारे पास पूर्ण नियंत्रण है, और हम इकाई परीक्षणों के साथ अपने Publisher मॉड्यूल को आसानी से कवर कर सकते हैं।


यदि हम कवरेज की गणना करते हैं, तो हमें यह प्राप्त करना चाहिए:


छवि अहमद तारेक द्वारा


जैसा कि आप देख सकते हैं, Publisher मॉड्यूल 100% कवर किया गया है। बाकी के लिए, यह इस लेख के दायरे से बाहर है, लेकिन यदि आप लेख में दिए गए दृष्टिकोण का पालन करते हैं तो आप इसे आसानी से कवर कर सकते हैं यूनिट टेस्ट के साथ .NET C# कंसोल एप्लिकेशन को पूरी तरह से कैसे कवर करें .


अनस्प्लैश पर जिंगडा चेन द्वारा फोटो, अहमद तारेक द्वारा समायोजित

अंतिम शब्द

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


यदि आप स्वयं को और अधिक प्रशिक्षित करना चाहते हैं, तो आप कुछ सर्वोत्तम अभ्यासों के बारे में मेरे अन्य लेख देख सकते हैं।


बस इतना ही, आशा है कि आपको यह लेख पढ़ने में उतना ही रोचक लगा होगा जितना मुझे इसे लिखने में लगा।


यहाँ भी प्रकाशित हुआ