paint-brush
.NET C# এ টাইমার ব্যবহার করার সর্বোত্তম উপায়দ্বারা@ahmedtarekhasan
4,384 পড়া
4,384 পড়া

.NET C# এ টাইমার ব্যবহার করার সর্বোত্তম উপায়

দ্বারা Ahmed Tarek Hasan30m2023/03/29
Read on Terminal Reader
Read this story w/o Javascript

অতিদীর্ঘ; পড়তে

আপনার C# অ্যাপ্লিকেশনে 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 ব্যবহার করব।


সমাধানের গঠনটি এইরকম হওয়া উচিত:


ছবি আহমদ তারেক


এটি শুধুমাত্র একটি কনসোল টাইমারঅ্যাপ প্রকল্পের সাথে একটি ব্যবহার টাইমার সমাধান।


আমি ইচ্ছাকৃতভাবে 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. এই ক্লাসটি আপডেট করার জন্য মাইক্রোসফ্টের জন্য একটি পরিবর্তন অনুরোধের টিকিট খোলা আছে, কিন্তু এই নিবন্ধটি লেখার মুহূর্ত পর্যন্ত, কোন পরিবর্তন প্রয়োগ করা হয়নি।


  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(); } } }


এখানে, আমরা এখনও অনেক কিছু করছি না। এটি প্রায় পুরানো সমাধান হিসাবে একই।


এটি চালানো এই মত কিছু সঙ্গে শেষ হওয়া উচিত:


ছবি আহমদ তারেক


Unsplash-এ 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# কনসোল অ্যাপ্লিকেশনটি ইউনিট পরীক্ষা দিয়ে কভার করবেন .


আনস্প্ল্যাশে জিংদা চেনের ছবি, আহমেদ তারেক অ্যাডজাস্ট করেছেন

চূড়ান্ত শব্দ

তুমি এটা করতে পার. এটি শুধুমাত্র বড় মডিউলগুলিকে ছোটগুলিতে বিভক্ত করা, আপনার বিমূর্ততাগুলিকে সংজ্ঞায়িত করা, জটিল অংশগুলির সাথে সৃজনশীল হওয়া এবং তারপরে আপনার কাজ শেষ।


আপনি যদি নিজেকে আরও প্রশিক্ষিত করতে চান তবে আপনি কিছু সেরা অনুশীলন সম্পর্কে আমার অন্যান্য নিবন্ধগুলি পরীক্ষা করতে পারেন।


এটাই, আশা করি আপনি এই নিবন্ধটি পড়ার মতো আকর্ষণীয় পেয়েছেন যেমনটি আমি এটি লিখতে পেয়েছি।


এছাড়াও এখানে প্রকাশিত