আপনার .NET C# অ্যাপ্লিকেশনে System.Timers.Timer ব্যবহার করার সময়, আপনি এটিকে বিমূর্ত করতে এবং ইউনিট টেস্টের মাধ্যমে আপনার মডিউলগুলিকে কভার করতে সক্ষম হতে সমস্যার সম্মুখীন হতে পারেন।
এই নিবন্ধে, আমরা কীভাবে এই চ্যালেঞ্জগুলিকে জয় করতে পারি তার সেরা অনুশীলনগুলি নিয়ে আলোচনা করব এবং শেষ পর্যন্ত, আপনি আপনার মডিউলগুলির 100% কভারেজ অর্জন করতে সক্ষম হবেন।
এইভাবে আমরা আমাদের সমাধানে যেতে যাচ্ছি:
কাজ করার জন্য একটি খুব সাধারণ উদাহরণ নিয়ে আসুন।
সহজ খারাপ সমাধান দিয়ে শুরু করুন।
আমরা চূড়ান্ত ফর্ম্যাটে না পৌঁছানো পর্যন্ত এটি উন্নত করার চেষ্টা চালিয়ে যান।
আমাদের ভ্রমণের মাধ্যমে শেখা পাঠের সারসংক্ষেপ।
আমাদের উদাহরণে, আমরা একটি সাধারণ কনসোল অ্যাপ্লিকেশন তৈরি করব যা শুধুমাত্র একটি সাধারণ কাজ করবে: প্রতি সেকেন্ডে তারিখ এবং সময় কনসোলে লিখতে একটি System.Timers.Timer
ব্যবহার করুন।
শেষ পর্যন্ত, আপনার এটি শেষ করা উচিত:
আপনি দেখতে পাচ্ছেন, এটি প্রয়োজনীয়তার পরিপ্রেক্ষিতে সহজ, অভিনব কিছুই নয়।
এই নিবন্ধে লক্ষ্য করা অন্যান্য সর্বোত্তম অনুশীলনের দিকে মূল ফোকাস চালানোর জন্য কিছু সেরা অনুশীলন উপেক্ষা/বাদ দেওয়া হবে।
এই নিবন্ধে, আমরা ইউনিট পরীক্ষা সহ System.Timers.Timer ব্যবহার করে মডিউলটি কভার করার উপর ফোকাস করব। যাইহোক, বাকি সমাধান ইউনিট পরীক্ষার সঙ্গে আচ্ছাদিত করা হবে না। আপনি যদি এই সম্পর্কে আরও জানতে চান, আপনি নিবন্ধটি পরীক্ষা করতে পারেন
কিছু তৃতীয় পক্ষের লাইব্রেরি আছে যা প্রায় একই রকম ফলাফল অর্জন করতে ব্যবহার করা যেতে পারে। যাইহোক, যখনই সম্ভব, আমি একটি সম্পূর্ণ বড় তৃতীয় পক্ষের লাইব্রেরির উপর নির্ভর করার চেয়ে একটি নেটিভ সাধারণ নকশা অনুসরণ করব।
এই সমাধানে, আমরা বিমূর্ততার একটি স্তর প্রদান না করে সরাসরি System.Timers.Timer ব্যবহার করব।
সমাধানের গঠনটি এইরকম হওয়া উচিত:
এটি শুধুমাত্র একটি কনসোল টাইমারঅ্যাপ প্রকল্পের সাথে একটি ব্যবহার টাইমার সমাধান।
আমি ইচ্ছাকৃতভাবে 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
সম্পত্তি শুধুমাত্র পঠনযোগ্য। অতএব, আপনি এটিকে একটি শিশু শ্রেণিতে ওভাররাইড করতে সক্ষম হবেন না।
এই ক্লাসটি আপডেট করার জন্য মাইক্রোসফ্টের জন্য একটি পরিবর্তন অনুরোধের টিকিট খোলা আছে, কিন্তু এই নিবন্ধটি লেখার মুহূর্ত পর্যন্ত, কোন পরিবর্তন প্রয়োগ করা হয়নি।
এছাড়াও, নোট করুন যে 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% আচ্ছাদিত। বাকিদের জন্য, এটি এই নিবন্ধের সুযোগের বাইরে, তবে আপনি যদি নিবন্ধের পদ্ধতি অনুসরণ করেন তবে আপনি এটিকে কভার করতে পারেন
তুমি এটা করতে পার. এটি শুধুমাত্র বড় মডিউলগুলিকে ছোটগুলিতে বিভক্ত করা, আপনার বিমূর্ততাগুলিকে সংজ্ঞায়িত করা, জটিল অংশগুলির সাথে সৃজনশীল হওয়া এবং তারপরে আপনার কাজ শেষ।
আপনি যদি নিজেকে আরও প্রশিক্ষিত করতে চান তবে আপনি কিছু সেরা অনুশীলন সম্পর্কে আমার অন্যান্য নিবন্ধগুলি পরীক্ষা করতে পারেন।
এটাই, আশা করি আপনি এই নিবন্ধটি পড়ার মতো আকর্ষণীয় পেয়েছেন যেমনটি আমি এটি লিখতে পেয়েছি।
এছাড়াও এখানে প্রকাশিত