dotnet-testing-test-output-logging
npx skills add https://github.com/kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-test-output-logging
Agent 安装分布
Skill 文档
測試輸åºèè¨éå°å®¶æå
æ¬æè½å婿¨å¨ .NET xUnit æ¸¬è©¦å°æ¡ä¸å¯¦ä½é«åè³ªçæ¸¬è©¦è¼¸åºèè¨éæ©å¶ã
æ ¸å¿åå
1. ITestOutputHelper 使ç¨åå
æ£ç¢ºçæ³¨å ¥æ¹å¼
- éé建æ§å¼æ³¨å
¥
ITestOutputHelper - æ¯å測試é¡å¥ç實ä¾èæ¸¬è©¦æ¹æ³ç¶å®
- ä¸å¯å¨éæ æ¹æ³æè·¨æ¸¬è©¦æ¹æ³éå ±ç¨
public class MyTests
{
private readonly ITestOutputHelper _output;
public MyTests(ITestOutputHelper testOutputHelper)
{
_output = testOutputHelper;
}
}
常è¦é¯èª¤
- â éæ
ååï¼
private static ITestOutputHelper _output - â å¨éåæ¥æ¸¬è©¦ä¸æªçå¾ å³ä½¿ç¨
- â åè©¦å¨ Dispose æ¹æ³ä¸ä½¿ç¨
2. çµæ§åè¼¸åºæ ¼å¼è¨è¨
建è°ç輸åºçµæ§
private void LogSection(string title)
{
_output.WriteLine($"\n=== {title} ===");
}
private void LogKeyValue(string key, object value)
{
_output.WriteLine($"{key}: {value}");
}
private void LogTimestamp(DateTime time)
{
_output.WriteLine($"å·è¡æé: {time:yyyy-MM-dd HH:mm:ss.fff}");
}
è¼¸åºææ©
- 測試éå§æï¼è¨é測試è¨ç½®èè¼¸å ¥è³æ
- å·è¡éç¨ä¸ï¼è¨ééè¦ççæ è®å
- æ·è¨åï¼è¨éé æå¼è實éå¼
- æ¸¬è©¦çµææï¼è¨éå·è¡æéèçµææè¦
3. ILogger 測試çç¥
ææ°ï¼æ´å æ¹æ³ç¡æ³ç´æ¥ Mock
ILogger.LogError() æ¯æ´å
æ¹æ³ï¼NSubstitute ç¡æ³ç´æ¥ææªãéè¦ææªåºå±¤ç Log<TState> æ¹æ³ï¼
// â é¯èª¤ï¼ç´æ¥ Mock æ´å
æ¹æ³æå¤±æ
logger.Received().LogError(Arg.Any<string>());
// â
æ£ç¢ºï¼ææªåºå±¤æ¹æ³
logger.Received().Log(
LogLevel.Error,
Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString().Contains("é æè¨æ¯")),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()
);
è§£æ±ºæ¹æ¡ï¼ä½¿ç¨æ½è±¡å±¤
å»ºç« AbstractLogger<T> ä¾ç°¡å測試ï¼
public abstract class AbstractLogger<T> : ILogger<T>
{
public IDisposable BeginScope<TState>(TState state)
=> null;
public bool IsEnabled(LogLevel logLevel)
=> true;
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
{
Log(logLevel, exception, state?.ToString() ?? string.Empty);
}
public abstract void Log(LogLevel logLevel, Exception ex, string information);
}
測試æä½¿ç¨
var logger = Substitute.For<AbstractLogger<MyService>>();
// ç¾å¨å¯ä»¥ç°¡å®é©è
logger.Received().Log(LogLevel.Error, Arg.Any<Exception>(), Arg.Is<string>(s => s.Contains("é¯èª¤è¨æ¯")));
4. 診æ·å·¥å ·æ´å
XUnitLoggerï¼å°è¨éå°å測試輸åº
public class XUnitLogger<T> : ILogger<T>
{
private readonly ITestOutputHelper _testOutputHelper;
public XUnitLogger(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
var message = formatter(state, exception);
_testOutputHelper.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] [{logLevel}] [{typeof(T).Name}] {message}");
if (exception != null)
{
_testOutputHelper.WriteLine($"Exception: {exception}");
}
}
// å
¶ä»å¿
è¦çä»é¢å¯¦ä½...
}
CompositeLoggerï¼åææ¯æ´é©èè輸åº
public class CompositeLogger<T> : ILogger<T>
{
private readonly ILogger<T>[] _loggers;
public CompositeLogger(params ILogger<T>[] loggers)
{
_loggers = loggers;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
foreach (var logger in _loggers)
{
logger.Log(logLevel, eventId, state, exception, formatter);
}
}
// å
¶ä»ä»é¢å¯¦ä½æå§æ´¾çµ¦ææå
§é¨ logger...
}
ä½¿ç¨æ¹å¼
// åæé²è¡è¡çºé©èèæ¸¬è©¦è¼¸åº
var mockLogger = Substitute.For<AbstractLogger<MyService>>();
var xunitLogger = new XUnitLogger<MyService>(_output);
var compositeLogger = new CompositeLogger<MyService>(mockLogger, xunitLogger);
var service = new MyService(compositeLogger);
坦使å
æè½æ¸¬è©¦ä¸çæéé»è¨é
[Fact]
public async Task ProcessLargeDataSet_æè½æ¸¬è©¦()
{
// Arrange
var stopwatch = Stopwatch.StartNew();
var checkpoints = new List<(string Stage, TimeSpan Elapsed)>();
_output.WriteLine("éå§èç大åè³æé...");
// Act & Monitor
await processor.LoadData(dataSet);
checkpoints.Add(("è³æè¼å
¥", stopwatch.Elapsed));
_output.WriteLine($"è³æè¼å
¥å®æ: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
await processor.ProcessData();
checkpoints.Add(("è³æèç", stopwatch.Elapsed));
_output.WriteLine($"è³æèç宿: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
stopwatch.Stop();
// Assert & Report
_output.WriteLine("\n=== æè½å ±å ===");
foreach (var (stage, elapsed) in checkpoints)
{
_output.WriteLine($"{stage}: {elapsed.TotalMilliseconds:F2} ms");
}
}
è¨ºæ·æ¸¬è©¦åºåºé¡å¥
public abstract class DiagnosticTestBase
{
protected readonly ITestOutputHelper Output;
protected DiagnosticTestBase(ITestOutputHelper output)
{
Output = output;
}
protected void LogTestStart(string testName)
{
Output.WriteLine($"\n=== {testName} ===");
Output.WriteLine($"å·è¡æé: {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}");
}
protected void LogTestData(object data)
{
Output.WriteLine($"æ¸¬è©¦è³æ: {JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true })}");
}
protected void LogAssertionFailure(string field, object expected, object actual)
{
Output.WriteLine("\n=== æ·è¨å¤±æ ===");
Output.WriteLine($"æ¬ä½: {field}");
Output.WriteLine($"é æå¼: {expected}");
Output.WriteLine($"實éå¼: {actual}");
}
}
DO – 建è°åæ³
-
é©ç¶ä½¿ç¨ ITestOutputHelper
- â å¨è¤é測試ä¸è¨ééè¦æ¥é©
- â æ¡ç¨ä¸è´ççµæ§åè¼¸åºæ ¼å¼
- â æ¸¬è©¦å¤±æææä¾è¨ºæ·è³è¨
- â å¨æè½æ¸¬è©¦ä¸è¨éæéé»
-
Logger 測試çç¥
- â ä½¿ç¨æ½è±¡å±¤ï¼AbstractLoggerï¼ç°¡å測試
- â é©èè¨é層ç´èé宿´è¨æ¯
- â ä½¿ç¨ CompositeLogger çµå Mock è實é輸åº
- â ç¢ºä¿ææè³æä¸è¢«è¨é
-
çµæ§å輸åº
- â 使ç¨ç« ç¯æ¨é¡åéä¸åéæ®µ
- â å 嫿鿳è¨ä¾¿æ¼è¿½è¹¤
- â æä¾è¶³å¤ çä¸ä¸æè³è¨
DON’T – é¿å åæ³
-
ä¸è¦é度使ç¨è¼¸åº
- â é¿å 卿¯å測試ä¸é½å¤§é輸åº
- â ä¸è¦è¨éææè³è¨ï¼å¯ç¢¼ãéé°ï¼
- â é¿å å½±é¿æ¸¬è©¦å·è¡æè½
-
ä¸è¦ç¡¬ç·¨ç¢¼è¨éé©è
- â é¿å é©è宿´çè¨éè¨æ¯ï¼æç¢ï¼
- â ä¸è¦é©èè¨éå¼å«çç¢ºåæ¬¡æ¸ï¼é度æå®ï¼
- â é¿å æ¸¬è©¦å §é¨å¯¦ä½ç´°ç¯
-
ä¸è¦å¿½ç¥çå½é±æ
- â ä¸è¦å¨éæ æ¹æ³ä¸ä½¿ç¨ ITestOutputHelper
- â ä¸è¦åè©¦è·¨æ¸¬è©¦æ¹æ³å ±ç¨å¯¦ä¾
- â é¿å å¨éåæ¥æ¸¬è©¦ä¸éºæ¼çå¾
ç¯ä¾åè
åè templates/ ç®éä¸ç宿´ç¯ä¾ï¼
itestoutputhelper-example.cs– ITestOutputHelper 使ç¨ç¯ä¾ilogger-testing-example.cs– ILogger 測試çç¥ç¯ä¾diagnostic-tools.cs– XUnitLogger è CompositeLogger 實ä½
åèè³æº
åå§æç«
æ¬æè½å §å®¹æç èªãèæ´¾è»é«å·¥ç¨å¸«ç測試修練 – 30 å¤©ææ°ãç³»åæç« ï¼
- Day 08 – 測試輸åºèè¨éï¼xUnit ITestOutputHelper è ILogger
- éµäººè³½æç« ï¼https://ithelp.ithome.com.tw/articles/10374711
- ç¯ä¾ç¨å¼ç¢¼ï¼https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day08
宿¹æä»¶
ç¸éæè½
unit-test-fundamentals– å®å 測試åºç¤xunit-project-setup– xUnit å°æ¡è¨å®nsubstitute-mocking– 測試æ¿èº«è模æ¬
æ¸¬è©¦æ¸ å®
å¨å¯¦ä½æ¸¬è©¦è¼¸åºèè¨éæï¼ç¢ºèªä»¥ä¸æª¢æ¥é ç®ï¼
- ITestOutputHelper éé建æ§å¼æ£ç¢ºæ³¨å ¥
- 使ç¨çµæ§åçè¼¸åºæ ¼å¼ï¼ç« ç¯ãæéæ³è¨ï¼
- Logger æ¸¬è©¦ä½¿ç¨æ½è±¡å±¤æ CompositeLogger
- é©èè¨é層ç´èé宿´è¨æ¯
- æè½æ¸¬è©¦å 嫿éé»è¨é
- æ²æå¨è¼¸åºä¸æ´©æ¼ææè³è¨
- éåæ¥æ¸¬è©¦æ£ç¢ºçå¾ è¨é宿
- æ¸¬è©¦å¤±æææä¾è¶³å¤ ç診æ·è³è¨