paint-brush
Cách tốt nhất để sử dụng bộ hẹn giờ trong .NET C#từ tác giả@ahmedtarekhasan
4,384 lượt đọc
4,384 lượt đọc

Cách tốt nhất để sử dụng bộ hẹn giờ trong .NET C#

từ tác giả Ahmed Tarek Hasan30m2023/03/29
Read on Terminal Reader

dài quá đọc không nổi

Sử dụng System.Timers.Timer trong ứng dụng C# của bạn, bạn có thể gặp vấn đề với việc trừu tượng hóa nó và có thể bao quát các mô-đun của mình bằng Bài kiểm tra đơn vị. Trong bài viết này, chúng ta sẽ thảo luận về các phương pháp hay nhất về cách chinh phục những thử thách này. Cuối cùng, bạn sẽ có thể đạt được phạm vi bảo hiểm 100% cho các mô-đun của mình.
featured image - Cách tốt nhất để sử dụng bộ hẹn giờ trong .NET C#
Ahmed Tarek Hasan HackerNoon profile picture

Làm thế nào để có toàn quyền kiểm soát bộ đếm thời gian và có thể đạt được mức độ phù hợp 100% với các bài kiểm tra đơn vị

Khi sử dụng System.Timers.Timer trong ứng dụng .NET C# của bạn, bạn có thể gặp sự cố khi trừu tượng hóa nó và có thể bao gồm các mô-đun của mình bằng Bài kiểm tra đơn vị.


Trong bài viết này, chúng ta sẽ thảo luận về Các phương pháp hay nhất về cách chinh phục những thách thức này và cuối cùng, bạn sẽ có thể đạt được mức độ phù hợp 100% cho các mô-đun của mình.


Ảnh của Lina Trochez trên Bapt

Tiếp cận

Đây là cách chúng ta sẽ tiếp cận giải pháp của mình:


  1. Hãy đến với một ví dụ rất đơn giản để làm việc trên.


  2. Bắt đầu với giải pháp xấu đơn giản.


  3. Tiếp tục cố gắng nâng cao nó cho đến khi chúng tôi đạt được định dạng cuối cùng.


  4. Tổng kết những bài học kinh nghiệm qua hành trình của chúng tôi.


Ảnh của James Harrison trên Bapt

ví dụ

Trong ví dụ của chúng tôi, chúng tôi sẽ xây dựng một Ứng dụng bảng điều khiển đơn giản chỉ thực hiện một việc đơn giản: sử dụng System.Timers.Timer để ghi vào bảng điều khiển ngày và giờ mỗi giây .


Cuối cùng, bạn nên kết thúc với điều này:


Hình ảnh của Ahmed Tarek


Như bạn có thể thấy, nó đơn giản về mặt yêu cầu, không có gì lạ mắt.


Ảnh của Mikael Seegen trên Unsplash, được điều chỉnh bởi Ahmed Tarek

từ chối trách nhiệm

  1. Một số phương pháp hay nhất sẽ bị bỏ qua/bỏ qua để hướng trọng tâm chính đến các phương pháp hay nhất khác được nhắm mục tiêu trong bài viết này.


  2. Trong bài viết này, chúng tôi sẽ tập trung vào việc trình bày mô-đun bằng cách sử dụng System.Timers.Timer với các bài kiểm tra đơn vị. Tuy nhiên, phần còn lại của giải pháp sẽ không nằm trong các bài kiểm tra đơn vị. Nếu bạn muốn biết thêm về điều này, bạn có thể kiểm tra bài viết Cách bao quát toàn bộ ứng dụng bảng điều khiển .NET C# với các bài kiểm tra đơn vị .


  3. Có một số thư viện của bên thứ ba có thể được sử dụng để đạt được kết quả gần như tương tự. Tuy nhiên, bất cứ khi nào có thể, tôi thà làm theo một thiết kế đơn giản gốc hơn là phụ thuộc vào toàn bộ thư viện lớn của bên thứ ba.


Ảnh của Maria Teneva trên Bapt, được điều chỉnh bởi Ahmed Tarek

Giải pháp tồi

Trong giải pháp này, chúng tôi sẽ trực tiếp sử dụng System.Timers.Timer mà không cung cấp lớp trừu tượng.


Cấu trúc của giải pháp sẽ trông như thế này:


Hình ảnh của Ahmed Tarek


Đó là một giải pháp Sử dụng Timer chỉ với một dự án Console TimerApp .


Tôi đã cố ý đầu tư thời gian và công sức vào việc trừu tượng hóa System.Console thành IConsole để chứng minh rằng điều này sẽ không giải quyết được vấn đề của chúng tôi với Bộ hẹn giờ.


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


Chúng tôi chỉ cần sử dụng System.Console.WriteLine trong ví dụ của mình; đó là lý do tại sao đây là phương pháp trừu tượng duy nhất.


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


Chúng tôi chỉ có hai phương thức trên giao diện IPublisher : StartPublishingStopPublishing .


Bây giờ, để thực hiện:


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


Console chỉ là một trình bao bọc mỏng cho 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 là một triển khai đơn giản của IPublisher . Nó đang sử dụng System.Timers.Timer và chỉ cần cấu hình nó.


Nó có IConsole được định nghĩa là một phần phụ thuộc. Đây không phải là một thực hành tốt nhất theo quan điểm của tôi. Nếu bạn muốn hiểu ý tôi, bạn có thể kiểm tra bài viết Khi nào không sử dụng Bộ chứa DI, IoC và IoC trong .NET C# .


Tuy nhiên, chỉ vì mục đích đơn giản, chúng tôi sẽ chỉ thêm nó như một phần phụ thuộc vào hàm tạo.


Chúng tôi cũng đang đặt khoảng thời gian của Bộ hẹn giờ thành 1000 mili giây (1 giây) và đặt trình xử lý để ghi SignalTime của bộ hẹn giờ vào Bảng điều khiển.


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


Ở đây, trong lớp Program , chúng tôi không làm gì nhiều. Chúng ta chỉ đang tạo một thể hiện của lớp Publisher và bắt đầu xuất bản.


Chạy này sẽ kết thúc với một cái gì đó như thế này:


Hình ảnh của Ahmed Tarek


Bây giờ, câu hỏi là, nếu bạn định viết một bài kiểm tra đơn vị cho lớp Publisher , bạn có thể làm gì?


Thật không may, câu trả lời sẽ là: không quá nhiều .


Đầu tiên, bạn không tự tiêm Timer như một phần phụ thuộc. Điều này có nghĩa là bạn đang ẩn phần phụ thuộc bên trong lớp Publisher . Do đó, chúng tôi không thể chế nhạo hoặc khai thác Bộ hẹn giờ.


Thứ hai, giả sử rằng chúng ta đã sửa đổi mã để Bộ hẹn giờ hiện được đưa vào hàm tạo; Tuy nhiên, câu hỏi sẽ là, làm thế nào để viết một bài kiểm tra đơn vị và thay thế Bộ hẹn giờ bằng một bản giả hoặc sơ khai?


Tôi nghe thấy ai đó hét lên, "hãy gói Bộ hẹn giờ thành một khái niệm trừu tượng và đưa nó vào thay vì Bộ hẹn giờ."


Vâng, đúng vậy, tuy nhiên, nó không đơn giản như vậy. Có một số thủ thuật mà tôi sẽ giải thích trong phần tiếp theo.


Ảnh của Carson Masterson trên Unsplash, được điều chỉnh bởi Ahmed Tarek

Giải pháp tốt

Đây là thời gian cho một giải pháp tốt. Hãy xem những gì chúng ta có thể làm về nó.


Cấu trúc của giải pháp sẽ trông như thế này:


Hình ảnh của Ahmed Tarek


Đó là cùng một giải pháp Sử dụng Timer với dự án Console BetterTimerApp mới.


IConsole , IPublisherConsole sẽ giống nhau.

iTimer

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


Những gì chúng ta có thể nhận thấy ở đây:


  1. Chúng tôi đã xác định đại biểu mới TimerIntervalElapsedEventHandler . Đại biểu này đại diện cho sự kiện được đưa ra bởi ITimer của chúng tôi.


  2. Bạn có thể lập luận rằng chúng tôi không cần đại biểu mới này vì chúng tôi đã có ElapsedEventHandler gốc đã được sử dụng bởi System.Timers.Timer .


  3. Đúng vậy đây là sự thật. Tuy nhiên, bạn sẽ nhận thấy rằng sự kiện ElapsedEventHandler đang cung cấp ElapsedEventArgs làm đối số sự kiện. ElapsedEventArgs này có hàm tạo riêng và bạn sẽ không thể tạo phiên bản của riêng mình. Ngoài ra, thuộc tính SignalTime được xác định trong lớp ElapsedEventArgs là chỉ đọc. Do đó, bạn sẽ không thể ghi đè nó trong một lớp con.


  4. Có một vé yêu cầu thay đổi được mở để Microsoft cập nhật lớp này, nhưng cho đến thời điểm viết bài này, không có thay đổi nào được áp dụng.


  5. Ngoài ra, lưu ý rằng ITimer mở rộng IDisposable .



nhà xuất bản

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


Nó gần giống như Publisher cũ ngoại trừ những thay đổi nhỏ. Bây giờ, chúng ta có ITimer được định nghĩa là một phần phụ thuộc được đưa vào thông qua hàm tạo. Phần còn lại của mã sẽ giống nhau.

hẹn giờ

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


Đây là nơi gần như tất cả các phép thuật xảy ra.


Những gì chúng ta có thể nhận thấy ở đây:


  1. Trong nội bộ, chúng tôi đang sử dụng System.Timers.Timer .


  2. Chúng tôi đã áp dụng mẫu thiết kế IDisposable . Đó là lý do tại sao bạn có thể thấy private bool m_IsDisposed , public void Dispose() , protected virtual void Dispose(bool disposing)~Timer() .


  3. Trong hàm tạo, chúng tôi đang khởi tạo một thể hiện mới của System.Timers.Timer . Chúng tôi sẽ gọi đây là Bộ hẹn giờ bên trong trong các bước còn lại.


  4. Đối với public bool Enabled , public double Interval , public void Start()public void Stop() , chúng ta chỉ ủy quyền triển khai cho Internal Timer.


  5. Đối với public event TimerIntervalElapsedEventHandler TimerIntervalElapsed , đây là phần quan trọng nhất; vì vậy hãy phân tích nó từng bước một.


  6. Điều chúng ta cần làm với sự kiện này là xử lý khi ai đó đăng ký/hủy đăng ký từ bên ngoài. Trong trường hợp này, chúng tôi muốn phản chiếu điều này với Bộ hẹn giờ bên trong.


  7. Nói cách khác, nếu ai đó từ bên ngoài có phiên bản ITimer của chúng tôi, anh ta sẽ có thể làm điều gì đó như thế này t.TimerIntervalElapsed += (sender, dateTime) => { //do something } .


  8. Tại thời điểm này, điều chúng ta nên làm là thực hiện nội bộ một số thứ như m_Timer.Elapsed += (sender, elapsedEventArgs) => { //do something } .


  9. Tuy nhiên, chúng ta cần lưu ý rằng hai trình xử lý không giống nhau vì chúng thực sự thuộc các loại khác nhau; TimerIntervalElapsedEventHandlerElapsedEventHandler .


  10. Do đó, những gì chúng ta cần làm là gói phần sắp tới trong TimerIntervalElapsedEventHandler vào một ElapsedEventHandler bên trong mới. Đây là điều chúng ta có thể làm.


  11. Tuy nhiên, chúng ta cũng cần lưu ý rằng, tại một thời điểm nào đó, ai đó có thể cần hủy đăng ký trình xử lý khỏi sự kiện TimerIntervalElapsedEventHandler .


  12. Điều này có nghĩa là, tại thời điểm này, chúng tôi cần biết trình xử lý ElapsedEventHandler nào tương ứng với trình xử lý TimerIntervalElapsedEventHandler đó để chúng tôi có thể hủy đăng ký nó khỏi Bộ hẹn giờ nội bộ.


  13. Cách duy nhất để đạt được điều này là theo dõi từng trình xử lý TimerIntervalElapsedEventHandler và trình xử lý ElapsedEventHandler mới được tạo trong từ điển. Bằng cách này, bằng cách biết trình xử lý được truyền trong TimerIntervalElapsedEventHandler , chúng ta có thể biết trình xử lý ElapsedEventHandler tương ứng.


  14. Tuy nhiên, chúng ta cũng cần lưu ý rằng từ bên ngoài, ai đó có thể đăng ký cùng một trình xử lý TimerIntervalElapsedEventHandler nhiều lần.


  15. Vâng, điều này không hợp lý, nhưng vẫn có thể thực hiện được. Do đó, để hoàn thiện, đối với mỗi trình xử lý TimerIntervalElapsedEventHandler , chúng tôi sẽ giữ một danh sách các trình xử lý ElapsedEventHandler .


  16. Trong hầu hết các trường hợp, danh sách này sẽ chỉ có một mục nhập trừ trường hợp đăng ký trùng lặp.


  17. Và đó là lý do tại sao bạn có thể thấy 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); } } }


Trong add , chúng ta đang tạo một ElapsedEventHandler mới, thêm một bản ghi trong từ điển m_Handlers ánh xạ bản ghi này tới TimerIntervalElapsedEventHandler và cuối cùng đăng ký Internal Timer.


Trong remove , chúng tôi sẽ nhận được danh sách tương ứng của trình xử lý ElapsedEventHandler , chọn trình xử lý cuối cùng, hủy đăng ký nó khỏi Bộ hẹn giờ nội bộ, xóa nó khỏi danh sách và xóa toàn bộ mục nhập nếu danh sách trống.


Cũng đáng nói, việc thực hiện 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; }


Chúng tôi đang hủy đăng ký tất cả các trình xử lý còn lại khỏi Bộ hẹn giờ bên trong, loại bỏ Bộ hẹn giờ bên trong và xóa từ điển m_Handlers .

Chương trình

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


Ở đây, chúng tôi vẫn chưa làm được gì nhiều. Nó gần giống như giải pháp cũ.


Chạy này sẽ kết thúc với một cái gì đó như thế này:


Hình ảnh của Ahmed Tarek


Ảnh của Testalize.me trên Unsplash, được điều chỉnh bởi Ahmed Tarek

Thời gian thử nghiệm, khoảnh khắc của sự thật

Bây giờ, chúng tôi có thiết kế cuối cùng của chúng tôi. Tuy nhiên, chúng tôi cần xem liệu thiết kế này có thực sự có thể giúp chúng tôi bao quát mô-đun Publisher của mình bằng các bài kiểm tra đơn vị hay không.


Cấu trúc của giải pháp sẽ trông như thế này:


Hình ảnh của Ahmed Tarek


Tôi đang sử dụng NUnitMoq để thử nghiệm. Bạn chắc chắn có thể làm việc với các thư viện ưa thích của mình.

hẹn giờStub

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


Những gì chúng ta có thể nhận thấy ở đây:


  1. Chúng tôi đã xác định Action enum sẽ được sử dụng trong khi ghi nhật ký các hành động được thực hiện thông qua sơ khai Bộ hẹn giờ của chúng tôi. Điều này sẽ được sử dụng sau này để xác nhận các hành động nội bộ được thực hiện.


  2. Ngoài ra, chúng tôi đã định nghĩa lớp ActionLog sẽ được sử dụng để ghi nhật ký.


  3. Chúng tôi đã định nghĩa lớp TimerStub là sơ khai của ITimer . Chúng tôi sẽ sử dụng sơ khai này sau khi kiểm tra mô-đun Publisher .


  4. Việc thực hiện rất đơn giản. Điều đáng nói là chúng tôi đã thêm một phương thức public void TriggerTimerIntervalElapsed(DateTime dateTime) để chúng tôi có thể kích hoạt sơ khai theo cách thủ công trong một bài kiểm tra đơn vị.


  5. Chúng tôi cũng có thể chuyển vào giá trị dự kiến của dateTime để chúng tôi có một giá trị đã biết để xác nhận.

nhà xuất bản

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


Bây giờ, như bạn có thể thấy, chúng tôi có toàn quyền kiểm soát và chúng tôi có thể dễ dàng bao quát mô-đun Publisher của mình bằng các bài kiểm tra đơn vị.


Nếu chúng tôi tính toán phạm vi bảo hiểm, chúng tôi sẽ nhận được điều này:


Hình ảnh của Ahmed Tarek


Như bạn có thể thấy, mô-đun Publisher được bao phủ 100%. Đối với phần còn lại, điều này nằm ngoài phạm vi của bài viết này, nhưng bạn có thể chỉ cần giải quyết nó nếu bạn làm theo cách tiếp cận trong bài viết Cách bao quát toàn bộ ứng dụng bảng điều khiển .NET C# với các bài kiểm tra đơn vị .


Ảnh của Jingda Chen trên Unsplash, được điều chỉnh bởi Ahmed Tarek

Từ cuối cùng

Bạn có thể làm được. Nó chỉ là vấn đề chia nhỏ các mô-đun lớn thành các mô-đun nhỏ hơn, xác định các phần trừu tượng của bạn, sáng tạo với các phần phức tạp, và sau đó bạn đã hoàn thành.


Nếu bạn muốn rèn luyện bản thân nhiều hơn, bạn có thể xem các bài viết khác của tôi về một số Phương pháp hay nhất.


Vậy đó, hy vọng bạn thấy thú vị khi đọc bài viết này như tôi thấy khi viết nó.


Cũng được xuất bản ở đây