#0021 增加测试工程
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using System.Windows.Media.Imaging;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.ViewModels;
|
||||
using XplorePlane.Tests.Helpers;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace XplorePlane.Tests.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// 测试 PipelineExecutionService(任务 6.6)
|
||||
/// </summary>
|
||||
public class PipelineExecutionServiceTests
|
||||
{
|
||||
private readonly Mock<IImageProcessingService> _mockImageSvc;
|
||||
private readonly PipelineExecutionService _svc;
|
||||
private readonly BitmapSource _testBitmap;
|
||||
|
||||
public PipelineExecutionServiceTests()
|
||||
{
|
||||
_mockImageSvc = TestHelpers.CreateMockImageService();
|
||||
_svc = new PipelineExecutionService(_mockImageSvc.Object);
|
||||
_testBitmap = TestHelpers.CreateTestBitmap();
|
||||
}
|
||||
|
||||
private static PipelineNodeViewModel MakeNode(string key, int order = 0, bool enabled = true)
|
||||
{
|
||||
var node = new PipelineNodeViewModel(key, key + "算子") { Order = order, IsEnabled = enabled };
|
||||
return node;
|
||||
}
|
||||
|
||||
// ── 空流水线 ──────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task EmptyPipeline_ReturnsSourceImage()
|
||||
{
|
||||
var result = await _svc.ExecutePipelineAsync(
|
||||
Enumerable.Empty<PipelineNodeViewModel>(), _testBitmap);
|
||||
|
||||
Assert.Same(_testBitmap, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NullNodes_ReturnsSourceImage()
|
||||
{
|
||||
var result = await _svc.ExecutePipelineAsync(null!, _testBitmap);
|
||||
|
||||
Assert.Same(_testBitmap, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AllDisabledNodes_ReturnsSourceImage()
|
||||
{
|
||||
var nodes = new[] { MakeNode("Blur", 0, enabled: false) };
|
||||
var result = await _svc.ExecutePipelineAsync(nodes, _testBitmap);
|
||||
|
||||
Assert.Same(_testBitmap, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NullSource_ThrowsArgumentNullException()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(
|
||||
() => _svc.ExecutePipelineAsync(Enumerable.Empty<PipelineNodeViewModel>(), null!));
|
||||
}
|
||||
|
||||
// ── 取消令牌 ──────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task CancelledToken_ThrowsOperationCanceledException()
|
||||
{
|
||||
// 让 ProcessImageAsync 在执行时检查取消令牌
|
||||
_mockImageSvc.Setup(s => s.ProcessImageAsync(
|
||||
It.IsAny<BitmapSource>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<IDictionary<string, object>>(),
|
||||
It.IsAny<IProgress<double>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns<BitmapSource, string, IDictionary<string, object>, IProgress<double>, CancellationToken>(
|
||||
(src, _, _, _, ct) =>
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
return Task.FromResult(src);
|
||||
});
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
var nodes = new[] { MakeNode("Blur", 0) };
|
||||
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(
|
||||
() => _svc.ExecutePipelineAsync(nodes, _testBitmap, cancellationToken: cts.Token));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreCancelledToken_ThrowsBeforeExecution()
|
||||
{
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
var nodes = new[] { MakeNode("Blur", 0) };
|
||||
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(
|
||||
() => _svc.ExecutePipelineAsync(nodes, _testBitmap, cancellationToken: cts.Token));
|
||||
|
||||
// ProcessImageAsync 不应被调用
|
||||
_mockImageSvc.Verify(s => s.ProcessImageAsync(
|
||||
It.IsAny<BitmapSource>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<IDictionary<string, object>>(),
|
||||
It.IsAny<IProgress<double>>(),
|
||||
It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
// ── 节点失败包装 ──────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task NodeThrows_WrappedAsPipelineExecutionException()
|
||||
{
|
||||
_mockImageSvc.Setup(s => s.ProcessImageAsync(
|
||||
It.IsAny<BitmapSource>(),
|
||||
"Blur",
|
||||
It.IsAny<IDictionary<string, object>>(),
|
||||
It.IsAny<IProgress<double>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ThrowsAsync(new InvalidOperationException("处理失败"));
|
||||
|
||||
var nodes = new[] { MakeNode("Blur", 0) };
|
||||
|
||||
var ex = await Assert.ThrowsAsync<PipelineExecutionException>(
|
||||
() => _svc.ExecutePipelineAsync(nodes, _testBitmap));
|
||||
|
||||
Assert.Equal("Blur", ex.FailedOperatorKey);
|
||||
Assert.Equal(0, ex.FailedNodeOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NodeReturnsNull_ThrowsPipelineExecutionException()
|
||||
{
|
||||
_mockImageSvc.Setup(s => s.ProcessImageAsync(
|
||||
It.IsAny<BitmapSource>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<IDictionary<string, object>>(),
|
||||
It.IsAny<IProgress<double>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((BitmapSource?)null);
|
||||
|
||||
var nodes = new[] { MakeNode("Blur", 0) };
|
||||
|
||||
await Assert.ThrowsAsync<PipelineExecutionException>(
|
||||
() => _svc.ExecutePipelineAsync(nodes, _testBitmap));
|
||||
}
|
||||
|
||||
// ── 进度回调 ──────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Progress_ReportedForEachNode()
|
||||
{
|
||||
var nodes = new[]
|
||||
{
|
||||
MakeNode("Blur", 0),
|
||||
MakeNode("Sharpen", 1)
|
||||
};
|
||||
|
||||
var reports = new List<PipelineProgress>();
|
||||
var progress = new Progress<PipelineProgress>(p => reports.Add(p));
|
||||
|
||||
await _svc.ExecutePipelineAsync(nodes, _testBitmap, progress);
|
||||
|
||||
// 等待 Progress 回调(Progress<T> 是异步的)
|
||||
await Task.Delay(50);
|
||||
|
||||
Assert.Equal(2, reports.Count);
|
||||
Assert.Equal(1, reports[0].CurrentStep);
|
||||
Assert.Equal(2, reports[1].CurrentStep);
|
||||
}
|
||||
|
||||
// ── 节点按 Order 排序执行 ─────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Nodes_ExecutedInOrderAscending()
|
||||
{
|
||||
var executionOrder = new List<string>();
|
||||
_mockImageSvc.Setup(s => s.ProcessImageAsync(
|
||||
It.IsAny<BitmapSource>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<IDictionary<string, object>>(),
|
||||
It.IsAny<IProgress<double>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns<BitmapSource, string, IDictionary<string, object>, IProgress<double>, CancellationToken>(
|
||||
(src, key, _, _, _) =>
|
||||
{
|
||||
executionOrder.Add(key);
|
||||
return Task.FromResult(src);
|
||||
});
|
||||
|
||||
// 故意乱序传入
|
||||
var nodes = new[]
|
||||
{
|
||||
MakeNode("Threshold", 2),
|
||||
MakeNode("Blur", 0),
|
||||
MakeNode("Sharpen", 1)
|
||||
};
|
||||
|
||||
await _svc.ExecutePipelineAsync(nodes, _testBitmap);
|
||||
|
||||
Assert.Equal(new[] { "Blur", "Sharpen", "Threshold" }, executionOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user