अपने .NET C# एप्लिकेशन में System.Timers.Timer का उपयोग करते समय, आपको इसे अमूर्त करने और यूनिट टेस्ट के साथ अपने मॉड्यूल को कवर करने में सक्षम होने में समस्या का सामना करना पड़ सकता है।
इस लेख में, हम इन चुनौतियों पर विजय पाने के सर्वोत्तम तरीकों पर चर्चा करेंगे, और अंत तक, आप अपने मॉड्यूल का 100% कवरेज हासिल करने में सक्षम होंगे।
हम अपने समाधान के लिए इस तरह से संपर्क करने जा रहे हैं:
काम करने के लिए एक बहुत ही सरल उदाहरण के साथ आओ।
सरल खराब समाधान से प्रारंभ करें।
जब तक हम अंतिम प्रारूप तक नहीं पहुंच जाते, तब तक इसे बढ़ाने का प्रयास करते रहें।
हमारी यात्रा के माध्यम से सीखे गए पाठों को सारांशित करना।
हमारे उदाहरण में, हम एक साधारण कंसोल एप्लिकेशन का निर्माण करेंगे जो केवल एक साधारण काम करेगा: कंसोल को दिनांक और समय प्रति सेकंड लिखने के लिए System.Timers.Timer
का उपयोग करें ।
अंत में, आपको इसके साथ समाप्त होना चाहिए:
जैसा कि आप देख सकते हैं, आवश्यकताओं के मामले में यह सरल है, कुछ भी फैंसी नहीं है।
इस आलेख में लक्षित अन्य सर्वोत्तम प्रथाओं पर मुख्य ध्यान केंद्रित करने के लिए कुछ सर्वोत्तम प्रथाओं को अनदेखा/छोड़ दिया जाएगा।
इस लेख में, हम इकाई परीक्षणों के साथ System.Timers.Timer का उपयोग करके मॉड्यूल को कवर करने पर ध्यान केंद्रित करेंगे। हालाँकि, शेष समाधान इकाई परीक्षणों में शामिल नहीं होंगे। यदि आप इसके बारे में अधिक जानना चाहते हैं, तो आप लेख को देख सकते हैं
कुछ तृतीय-पक्ष पुस्तकालय हैं जिनका उपयोग लगभग समान परिणाम प्राप्त करने के लिए किया जा सकता है। हालाँकि, जब भी संभव हो, मैं एक पूरी बड़ी तृतीय-पक्ष लाइब्रेरी पर निर्भर रहने के बजाय एक मूल सरल डिज़ाइन का अनुसरण करूँगा।
इस समाधान में, हम अमूर्तता की एक परत प्रदान किए बिना सीधे 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
को एक निर्भरता के रूप में परिभाषित किया गया है। यह मेरे दृष्टिकोण से सर्वोत्तम अभ्यास नहीं है। यदि आप समझना चाहते हैं कि मेरा क्या मतलब है, तो आप लेख की जांच कर सकते हैं
हालाँकि, केवल सादगी के लिए, हम इसे कंस्ट्रक्टर में निर्भरता के रूप में इंजेक्ट करेंगे।
हम टाइमर अंतराल को 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(); } }
हम यहाँ क्या देख सकते हैं:
हमने नए प्रतिनिधि TimerIntervalElapsedEventHandler
परिभाषित किया। यह प्रतिनिधि हमारे ITimer
द्वारा उठाई जाने वाली घटना का प्रतिनिधित्व करता है।
आप तर्क दे सकते हैं कि हमें इस नए प्रतिनिधि की आवश्यकता नहीं है क्योंकि हमारे पास पहले से ही देशी ElapsedEventHandler
है जो पहले से ही System.Timers.Timer
द्वारा उपयोग किया जाता है।
हा ये तो है। हालांकि, आप देखेंगे कि ElapsedEventHandler
ईवेंट ElapsedEventArgs
ईवेंट तर्कों के रूप में प्रदान कर रहा है। इस ElapsedEventArgs
में एक निजी कन्स्ट्रक्टर है, और आप अपना खुद का उदाहरण नहीं बना पाएंगे। इसके अतिरिक्त, ElapsedEventArgs
क्लास में परिभाषित SignalTime
प्रॉपर्टी केवल पढ़ने के लिए है। इसलिए, आप इसे चाइल्ड क्लास में ओवरराइड नहीं कर पाएंगे।
इस वर्ग को अद्यतन करने के लिए Microsoft के लिए एक परिवर्तन अनुरोध टिकट खोला गया है, लेकिन इस लेख को लिखे जाने तक, कोई परिवर्तन लागू नहीं किया गया था।
साथ ही, ध्यान दें कि 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); } } }
यहीं पर लगभग सारा जादू होता है।
हम यहाँ क्या देख सकते हैं:
आंतरिक रूप से, हम System.Timers.Timer
उपयोग कर रहे हैं।
हमने आईडीस्पोजेबल डिजाइन पैटर्न लागू किया। यही कारण है कि आप private bool m_IsDisposed
, public void Dispose()
, protected virtual void Dispose(bool disposing)
, और ~Timer()
देख सकते हैं।
निर्माता में, हम System.Timers.Timer
का एक नया उदाहरण प्रारंभ कर रहे हैं। हम इसे बाकी चरणों में आंतरिक टाइमर के रूप में संदर्भित करेंगे।
public bool Enabled
, public double Interval
, public void Start()
, और public void Stop()
के लिए, हम केवल आंतरिक टाइमर के कार्यान्वयन को सौंप रहे हैं।
public event TimerIntervalElapsedEventHandler TimerIntervalElapsed
के लिए, यह सबसे महत्वपूर्ण हिस्सा है; तो चलिए इसका स्टेप बाय स्टेप विश्लेषण करते हैं।
इस घटना के साथ हमें क्या करना है जब कोई बाहर से इसकी सदस्यता/सदस्यता समाप्त करता है। इस मामले में, हम इसे आंतरिक टाइमर पर प्रतिबिंबित करना चाहते हैं।
दूसरे शब्दों में, अगर किसी बाहरी व्यक्ति के पास हमारे ITimer
का उदाहरण है, तो उसे कुछ ऐसा करने में सक्षम होना t.TimerIntervalElapsed += (sender, dateTime) => { //do something }
इस समय, हमें क्या करना चाहिए आंतरिक रूप से m_Timer.Elapsed += (sender, elapsedEventArgs) => { //do something }
जैसा कुछ करना चाहिए।
हालाँकि, हमें यह ध्यान रखने की आवश्यकता है कि दो हैंडलर समान नहीं हैं क्योंकि वे वास्तव में विभिन्न प्रकार के हैं; TimerIntervalElapsedEventHandler
और ElapsedEventHandler
।
इसलिए, हमें आने वाले TimerIntervalElapsedEventHandler
को एक नए आंतरिक ElapsedEventHandler
में लपेटने की आवश्यकता है। यह कुछ ऐसा है जो हम कर सकते हैं।
हालाँकि, हमें यह भी ध्यान रखना होगा कि किसी समय किसी को TimerIntervalElapsedEventHandler
ईवेंट से हैंडलर की सदस्यता समाप्त करने की आवश्यकता हो सकती है।
इसका मतलब यह है कि, इस समय, हमें यह जानने में सक्षम होना चाहिए कि कौन सा ElapsedEventHandler
हैंडलर उस TimerIntervalElapsedEventHandler
हैंडलर से मेल खाता है ताकि हम इसे आंतरिक टाइमर से अनसब्सक्राइब कर सकें।
इसे प्राप्त करने का एकमात्र तरीका एक शब्दकोश में प्रत्येक TimerIntervalElapsedEventHandler
हैंडलर और नव निर्मित ElapsedEventHandler
हैंडलर का ट्रैक रखना है। इस तरह, TimerIntervalElapsedEventHandler
हैंडलर में पारित होने के बारे में जानकर, हम संबंधित ElapsedEventHandler
हैंडलर को जान सकते हैं।
हालाँकि, हमें यह भी ध्यान रखना होगा कि बाहर से कोई व्यक्ति एक ही TimerIntervalElapsedEventHandler
हैंडलर को एक से अधिक बार सब्सक्राइब कर सकता है।
हां, यह तार्किक नहीं है, लेकिन फिर भी, यह करने योग्य है। इसलिए, पूर्णता के लिए, प्रत्येक TimerIntervalElapsedEventHandler
हैंडलर के लिए, हम ElapsedEventHandler
हैंडलर की एक सूची रखेंगे।
ज्यादातर मामलों में, इस सूची में केवल एक प्रविष्टि होगी जब तक कि डुप्लिकेट सदस्यता के मामले में न हो।
और इसीलिए आप यह देख सकते हैं 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(); } } }
यहां हम अभी भी बहुत कुछ नहीं कर रहे हैं। यह लगभग पुराने समाधान जैसा ही है।
इसे चलाने से कुछ इस तरह समाप्त होना चाहिए:
अब, हमारे पास अपना अंतिम डिज़ाइन है। हालाँकि, हमें यह देखने की आवश्यकता है कि क्या यह डिज़ाइन वास्तव में हमारे 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; } } }
हम यहाँ क्या देख सकते हैं:
हमने अपने टाइमर स्टब के माध्यम से किए गए कार्यों को लॉग करते समय उपयोग किए जाने वाले Action
एनम को परिभाषित किया। इसका उपयोग बाद में की गई आंतरिक क्रियाओं पर जोर देने के लिए किया जाएगा।
साथ ही, हमने लॉगिंग के लिए उपयोग की जाने वाली ActionLog
क्लास को परिभाषित किया।
हमने TimerStub
क्लास को ITimer
के स्टब के रूप में परिभाषित किया। Publisher
मॉड्यूल का परीक्षण करते समय हम बाद में इस स्टब का उपयोग करेंगे।
कार्यान्वयन सरल है। यह उल्लेख करने योग्य है कि हमने एक अतिरिक्त public void TriggerTimerIntervalElapsed(DateTime dateTime)
विधि जोड़ी है ताकि हम इकाई परीक्षण के भीतर मैन्युअल रूप से स्टब को ट्रिगर कर सकें।
हम 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% कवर किया गया है। बाकी के लिए, यह इस लेख के दायरे से बाहर है, लेकिन यदि आप लेख में दिए गए दृष्टिकोण का पालन करते हैं तो आप इसे आसानी से कवर कर सकते हैं
आप यह कर सकते हैं। यह बड़े मॉड्यूल को छोटे में विभाजित करने, अपने सार को परिभाषित करने, मुश्किल भागों के साथ रचनात्मक होने की बात है, और फिर आप कर चुके हैं।
यदि आप स्वयं को और अधिक प्रशिक्षित करना चाहते हैं, तो आप कुछ सर्वोत्तम अभ्यासों के बारे में मेरे अन्य लेख देख सकते हैं।
बस इतना ही, आशा है कि आपको यह लेख पढ़ने में उतना ही रोचक लगा होगा जितना मुझे इसे लिखने में लगा।
यहाँ भी प्रकाशित हुआ