From cd407dc93c423cb777e281748a8585c3282d783d Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:19:32 +0800 Subject: [PATCH] =?UTF-8?q?test(logging):=20=E6=B7=BB=E5=8A=A0=E9=87=87?= =?UTF-8?q?=E6=A0=B7=E8=BF=87=E6=BB=A4=E5=99=A8=E5=92=8C=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E9=99=84=E5=8A=A0=E5=99=A8=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为SamplingFilter添加完整的单元测试覆盖采样率、时间窗口、线程安全等功能 - 为StatisticsAppender添加全面的单元测试验证统计计算、错误率跟踪、报告生成功能 - 测试各个日志级别和记录器的独立状态维护 - 验证多线程环境下的数据一致性 - 包含边界条件和异常情况的测试用例 --- .../logging/SamplingFilterTests.cs | 133 +++++++++++++ .../logging/StatisticsAppenderTests.cs | 186 ++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 GFramework.Core.Tests/logging/SamplingFilterTests.cs create mode 100644 GFramework.Core.Tests/logging/StatisticsAppenderTests.cs diff --git a/GFramework.Core.Tests/logging/SamplingFilterTests.cs b/GFramework.Core.Tests/logging/SamplingFilterTests.cs new file mode 100644 index 0000000..64370c3 --- /dev/null +++ b/GFramework.Core.Tests/logging/SamplingFilterTests.cs @@ -0,0 +1,133 @@ +using GFramework.Core.Abstractions.logging; +using GFramework.Core.logging.filters; +using NUnit.Framework; + +namespace GFramework.Core.Tests.logging; + +[TestFixture] +public class SamplingFilterTests +{ + [Test] + public void SamplingFilter_Should_Sample_Logs_By_Rate() + { + var filter = new SamplingFilter(sampleRate: 3, timeWindow: TimeSpan.FromMinutes(1)); + var entry = new LogEntry( + DateTime.UtcNow, + LogLevel.Info, + "TestLogger", + "Test message", + null, + null + ); + + // 前 3 条:第 1 条通过,第 2、3 条被过滤 + Assert.That(filter.ShouldLog(entry), Is.True); // 1st + Assert.That(filter.ShouldLog(entry), Is.False); // 2nd + Assert.That(filter.ShouldLog(entry), Is.False); // 3rd + + // 第 4 条通过(新周期) + Assert.That(filter.ShouldLog(entry), Is.True); // 4th + Assert.That(filter.ShouldLog(entry), Is.False); // 5th + Assert.That(filter.ShouldLog(entry), Is.False); // 6th + } + + [Test] + public void SamplingFilter_Should_Reset_After_Time_Window() + { + var filter = new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMilliseconds(100)); + var entry = new LogEntry( + DateTime.UtcNow, + LogLevel.Info, + "TestLogger", + "Test message", + null, + null + ); + + // 第一个窗口 + Assert.That(filter.ShouldLog(entry), Is.True); // 1st + Assert.That(filter.ShouldLog(entry), Is.False); // 2nd + + // 等待时间窗口过期 + Thread.Sleep(150); + + // 新窗口应该重置计数 + Assert.That(filter.ShouldLog(entry), Is.True); // 1st in new window + Assert.That(filter.ShouldLog(entry), Is.False); // 2nd in new window + } + + [Test] + public void SamplingFilter_Should_Maintain_Separate_State_Per_Logger() + { + var filter = new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromMinutes(1)); + + var entry1 = new LogEntry(DateTime.UtcNow, LogLevel.Info, "Logger1", "Message", null, null); + var entry2 = new LogEntry(DateTime.UtcNow, LogLevel.Info, "Logger2", "Message", null, null); + + // Logger1 的第一条 + Assert.That(filter.ShouldLog(entry1), Is.True); + + // Logger2 的第一条(独立计数) + Assert.That(filter.ShouldLog(entry2), Is.True); + + // Logger1 的第二条 + Assert.That(filter.ShouldLog(entry1), Is.False); + + // Logger2 的第二条 + Assert.That(filter.ShouldLog(entry2), Is.False); + } + + [Test] + public void SamplingFilter_Should_Throw_When_SampleRate_Is_Invalid() + { + Assert.Throws(() => + { + new SamplingFilter(sampleRate: 0, timeWindow: TimeSpan.FromMinutes(1)); + }); + + Assert.Throws(() => + { + new SamplingFilter(sampleRate: -1, timeWindow: TimeSpan.FromMinutes(1)); + }); + } + + [Test] + public void SamplingFilter_Should_Throw_When_TimeWindow_Is_Invalid() + { + Assert.Throws(() => { new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.Zero); }); + + Assert.Throws(() => + { + new SamplingFilter(sampleRate: 2, timeWindow: TimeSpan.FromSeconds(-1)); + }); + } + + [Test] + public void SamplingFilter_Should_Be_Thread_Safe() + { + var filter = new SamplingFilter(sampleRate: 10, timeWindow: TimeSpan.FromMinutes(1)); + var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Message", null, null); + + var passedCount = 0; + var tasks = new List(); + + for (int i = 0; i < 10; i++) + { + tasks.Add(Task.Run(() => + { + for (int j = 0; j < 100; j++) + { + if (filter.ShouldLog(entry)) + { + Interlocked.Increment(ref passedCount); + } + } + })); + } + + Task.WaitAll(tasks.ToArray()); + + // 1000 条日志,采样率 10,应该通过约 100 条 + Assert.That(passedCount, Is.InRange(90, 110)); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/logging/StatisticsAppenderTests.cs b/GFramework.Core.Tests/logging/StatisticsAppenderTests.cs new file mode 100644 index 0000000..902f8cd --- /dev/null +++ b/GFramework.Core.Tests/logging/StatisticsAppenderTests.cs @@ -0,0 +1,186 @@ +using GFramework.Core.Abstractions.logging; +using GFramework.Core.logging.appenders; +using NUnit.Framework; + +namespace GFramework.Core.Tests.logging; + +[TestFixture] +public class StatisticsAppenderTests +{ + [SetUp] + public void SetUp() + { + _appender = new StatisticsAppender(); + } + + [TearDown] + public void TearDown() + { + } + + private StatisticsAppender _appender = null!; + + [Test] + public void StatisticsAppender_Should_Count_Total_Logs() + { + var entry = CreateLogEntry(LogLevel.Info, "TestLogger", "Message"); + + _appender.Append(entry); + _appender.Append(entry); + _appender.Append(entry); + + Assert.That(_appender.TotalCount, Is.EqualTo(3)); + } + + [Test] + public void StatisticsAppender_Should_Count_Errors() + { + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger", "Info")); + _appender.Append(CreateLogEntry(LogLevel.Error, "Logger", "Error")); + _appender.Append(CreateLogEntry(LogLevel.Fatal, "Logger", "Fatal")); + _appender.Append(CreateLogEntry(LogLevel.Warning, "Logger", "Warning")); + + Assert.That(_appender.ErrorCount, Is.EqualTo(2)); // Error + Fatal + Assert.That(_appender.TotalCount, Is.EqualTo(4)); + } + + [Test] + public void StatisticsAppender_Should_Calculate_Error_Rate() + { + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger", "Info")); + _appender.Append(CreateLogEntry(LogLevel.Error, "Logger", "Error")); + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger", "Info")); + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger", "Info")); + + // 1 error out of 4 total = 0.25 + Assert.That(_appender.ErrorRate, Is.EqualTo(0.25).Within(0.001)); + } + + [Test] + public void StatisticsAppender_Should_Count_By_Level() + { + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger", "Info1")); + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger", "Info2")); + _appender.Append(CreateLogEntry(LogLevel.Error, "Logger", "Error")); + _appender.Append(CreateLogEntry(LogLevel.Warning, "Logger", "Warning")); + + Assert.That(_appender.GetCountByLevel(LogLevel.Info), Is.EqualTo(2)); + Assert.That(_appender.GetCountByLevel(LogLevel.Error), Is.EqualTo(1)); + Assert.That(_appender.GetCountByLevel(LogLevel.Warning), Is.EqualTo(1)); + Assert.That(_appender.GetCountByLevel(LogLevel.Debug), Is.EqualTo(0)); + } + + [Test] + public void StatisticsAppender_Should_Count_By_Logger() + { + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger1", "Message")); + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger1", "Message")); + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger2", "Message")); + + Assert.That(_appender.GetCountByLogger("Logger1"), Is.EqualTo(2)); + Assert.That(_appender.GetCountByLogger("Logger2"), Is.EqualTo(1)); + Assert.That(_appender.GetCountByLogger("Logger3"), Is.EqualTo(0)); + } + + [Test] + public void StatisticsAppender_Should_Track_Uptime() + { + var startTime = _appender.StartTime; + Thread.Sleep(100); + + Assert.That(_appender.Uptime, Is.GreaterThanOrEqualTo(TimeSpan.FromMilliseconds(100))); + Assert.That(_appender.StartTime, Is.EqualTo(startTime)); + } + + [Test] + public void StatisticsAppender_Should_Reset_Statistics() + { + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger", "Message")); + _appender.Append(CreateLogEntry(LogLevel.Error, "Logger", "Error")); + + Assert.That(_appender.TotalCount, Is.EqualTo(2)); + Assert.That(_appender.ErrorCount, Is.EqualTo(1)); + + var oldStartTime = _appender.StartTime; + Thread.Sleep(10); + _appender.Reset(); + + Assert.That(_appender.TotalCount, Is.EqualTo(0)); + Assert.That(_appender.ErrorCount, Is.EqualTo(0)); + Assert.That(_appender.GetLevelCounts().Count, Is.EqualTo(0)); + Assert.That(_appender.GetLoggerCounts().Count, Is.EqualTo(0)); + Assert.That(_appender.StartTime, Is.GreaterThan(oldStartTime)); + } + + [Test] + public void StatisticsAppender_Should_Generate_Report() + { + _appender.Append(CreateLogEntry(LogLevel.Info, "Logger1", "Info")); + _appender.Append(CreateLogEntry(LogLevel.Error, "Logger2", "Error")); + _appender.Append(CreateLogEntry(LogLevel.Warning, "Logger1", "Warning")); + + var report = _appender.GenerateReport(); + + Assert.That(report, Does.Contain("总日志数: 3")); + Assert.That(report, Does.Contain("错误日志数: 1")); + Assert.That(report, Does.Contain("Info")); + Assert.That(report, Does.Contain("Error")); + Assert.That(report, Does.Contain("Warning")); + Assert.That(report, Does.Contain("Logger1")); + Assert.That(report, Does.Contain("Logger2")); + } + + [Test] + public void StatisticsAppender_Should_Return_Empty_Collections_When_No_Data() + { + Assert.That(_appender.TotalCount, Is.EqualTo(0)); + Assert.That(_appender.ErrorCount, Is.EqualTo(0)); + Assert.That(_appender.ErrorRate, Is.EqualTo(0)); + Assert.That(_appender.GetLevelCounts().Count, Is.EqualTo(0)); + Assert.That(_appender.GetLoggerCounts().Count, Is.EqualTo(0)); + } + + [Test] + public void StatisticsAppender_Should_Be_Thread_Safe() + { + var tasks = new List(); + + for (int i = 0; i < 10; i++) + { + var loggerId = i; + tasks.Add(Task.Run(() => + { + for (int j = 0; j < 100; j++) + { + var level = j % 2 == 0 ? LogLevel.Info : LogLevel.Error; + _appender.Append(CreateLogEntry(level, $"Logger{loggerId}", "Message")); + } + })); + } + + Task.WaitAll(tasks.ToArray()); + + Assert.That(_appender.TotalCount, Is.EqualTo(1000)); + Assert.That(_appender.ErrorCount, Is.EqualTo(500)); + Assert.That(_appender.GetCountByLevel(LogLevel.Info), Is.EqualTo(500)); + Assert.That(_appender.GetCountByLevel(LogLevel.Error), Is.EqualTo(500)); + } + + [Test] + public void Flush_Should_Not_Throw() + { + Assert.DoesNotThrow(() => _appender.Flush()); + } + + private static LogEntry CreateLogEntry(LogLevel level, string loggerName, string message) + { + return new LogEntry( + DateTime.UtcNow, + level, + loggerName, + message, + null, + null + ); + } +} \ No newline at end of file