NavisworksTransport/UnitTests/Integration/AutoPathObstacleDetourAutomationTests.cs
tian 59e323f9bb feat: 集成测试补 T07-T10(P0+P1 全部完成,18/18 通过)
- T07 障碍绕行(AutoPathObstacleDetourAutomationTests):跨模型起终点直线 11.0m→规划 11.57m 绕行,blocked=0
- T08 多策略(同测试类):Shortest/Straightest 参数回传 + 均规划有效路径
- T09 网格诊断(RouteGridDiagnosticsAutomationTests):网格参数/路径点覆盖/段一致性
- T10 持久化(DetectionRecordPersistenceAutomationTests):检测记录重载验证(动画参数 15fps/5s 落库)
- 服务端:PathPlanningManager.GetDetectionRecordById + /api/test/detection-record 端点
- 客户端:RunAutoPathAsync 支持 strategy、GetRouteGridDiagnosticsAsync、GetDetectionRecordAsync

验证:集成测试 18/18 通过,完整集 65.8s
2026-08-04 18:21:18 +08:00

183 lines
9.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
namespace NavisworksTransport.UnitTests.Integration
{
/// <summary>
/// 自动路径障碍绕行集成测试(覆盖计划 T07
///
/// 构造起终点直线连线穿过障碍物的场景(跨越大半模型),验证:
/// - AutoPlanPath 成功规划绕行路径blockedSampleCount == 0路径不穿过不可通行网格
/// - 规划长度 > 起终点直线距离(确实绕行,而非直线硬穿)
///
/// 起终点坐标来自模型内已验证可用的区域(测试基准起点 → 自动测试_Ground 终点),
/// 直线距离由测试按服务端返回的 metersToModelUnits 换算(不硬编码文档单位)。
/// </summary>
[TestClass]
[TestCategory("NavisworksIntegration")]
public class AutoPathObstacleDetourAutomationTests
{
private const string RouteName = "障碍绕行_测试";
// 模型单位坐标:跨越大半模型的起终点(直线连线穿过障碍区域)
private const double StartX = -204.608;
private const double StartY = 14.833;
private const double StartZ = 19.250;
private const double EndX = -177.130;
private const double EndY = 14.833;
private const double EndZ = -4.383;
private const double ObjectLengthInMeters = 0.4;
private const double ObjectWidthInMeters = 0.4;
private const double SafetyMarginInMeters = 0.05;
private const double GridSizeInMeters = 0.3;
[TestMethod]
[Timeout(240000)]
public async Task GroundAutoPath_ObstacleRoute_DetoursAroundObstacles()
{
using (var client = new NavisworksTestAutomationClient())
{
await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);
string tempDir = Path.Combine(Path.GetTempPath(), "NavisworksTransportIntegrationTests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempDir);
try
{
// 1. 导入障碍起终点路径overwrite 保证可重复执行)
string filePath = Path.Combine(tempDir, "obstacle-route.json");
File.WriteAllText(filePath, BuildObstacleRouteJson(), new UTF8Encoding(false));
JObject importResponse = await client.ImportRouteFileAsync(filePath, overwrite: true).ConfigureAwait(false);
Assert.IsTrue(importResponse.Value<bool>("ok"), "导入障碍路径失败: " + (string)importResponse["error"]);
double metersToModelUnits = importResponse["data"].Value<double>("metersToModelUnits");
// 2. 自动路径规划
JObject response = await client.RunAutoPathAsync(
"Ground",
RouteName,
ObjectLengthInMeters,
ObjectWidthInMeters,
SafetyMarginInMeters,
GridSizeInMeters).ConfigureAwait(false);
Assert.IsTrue(response.Value<bool>("ok"), "自动路径规划失败: " + (string)response["error"]);
JObject data = (JObject)response["data"];
// 3. 路径成功绕行:不穿过不可通行网格
JObject segmentValidation = (JObject)data["segmentValidation"];
Assert.IsNotNull(segmentValidation, "缺少 segmentValidation");
Assert.AreEqual(0, (int)segmentValidation["blockedSampleCount"], "绕行路径仍穿过不可通行网格");
Assert.AreEqual(0, (int)segmentValidation["invalidSampleCount"], "绕行路径存在落在网格外的采样点");
// 4. 确实绕行:规划长度 > 起终点直线距离(换算为米)
double straightLineModelUnits = Math.Sqrt(
(EndX - StartX) * (EndX - StartX) +
(EndY - StartY) * (EndY - StartY) +
(EndZ - StartZ) * (EndZ - StartZ));
double straightLineMeters = straightLineModelUnits / metersToModelUnits;
double plannedLength = (double)data["generatedRoute"]["length"];
Assert.IsTrue(
plannedLength > straightLineMeters * 1.02,
$"规划路径长度 {plannedLength:F3}m 应明显大于直线距离 {straightLineMeters:F3}m未绕行");
}
finally
{
SafeDelete(tempDir);
}
}
}
[TestMethod]
[Timeout(240000)]
public async Task GroundAutoPath_Strategies_ShortestAndStraightestBothSucceed()
{
using (var client = new NavisworksTestAutomationClient())
{
await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);
string tempDir = Path.Combine(Path.GetTempPath(), "NavisworksTransportIntegrationTests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempDir);
try
{
string filePath = Path.Combine(tempDir, "obstacle-route.json");
File.WriteAllText(filePath, BuildObstacleRouteJson(), new UTF8Encoding(false));
JObject importResponse = await client.ImportRouteFileAsync(filePath, overwrite: true).ConfigureAwait(false);
Assert.IsTrue(importResponse.Value<bool>("ok"), "导入障碍路径失败: " + (string)importResponse["error"]);
// Shortest 策略
JObject shortest = await client.RunAutoPathAsync(
"Ground", RouteName,
ObjectLengthInMeters, ObjectWidthInMeters, SafetyMarginInMeters, GridSizeInMeters,
strategy: "Shortest").ConfigureAwait(false);
Assert.IsTrue(shortest.Value<bool>("ok"), "Shortest 策略规划失败: " + (string)shortest["error"]);
// Straightest 策略
JObject straightest = await client.RunAutoPathAsync(
"Ground", RouteName,
ObjectLengthInMeters, ObjectWidthInMeters, SafetyMarginInMeters, GridSizeInMeters,
strategy: "Straightest").ConfigureAwait(false);
Assert.IsTrue(straightest.Value<bool>("ok"), "Straightest 策略规划失败: " + (string)straightest["error"]);
// 策略参数回传正确
Assert.AreEqual("Shortest", (string)shortest["data"]["planningParameters"]["strategy"], "Shortest 策略参数未回传");
Assert.AreEqual("Straightest", (string)straightest["data"]["planningParameters"]["strategy"], "Straightest 策略参数未回传");
// 两种策略均规划出有效路径(不穿过障碍)
AssertValidPlannedPath(shortest, "Shortest");
AssertValidPlannedPath(straightest, "Straightest");
}
finally
{
SafeDelete(tempDir);
}
}
}
private static void AssertValidPlannedPath(JObject response, string strategyName)
{
JObject data = (JObject)response["data"];
JObject generatedRoute = (JObject)data["generatedRoute"];
Assert.IsTrue((int)generatedRoute["pointCount"] >= 2, $"{strategyName} 路径至少应包含起点和终点");
Assert.IsTrue((double)generatedRoute["length"] > 0, $"{strategyName} 路径长度必须大于 0");
JObject segmentValidation = (JObject)data["segmentValidation"];
Assert.AreEqual(0, (int)segmentValidation["blockedSampleCount"], $"{strategyName} 路径穿过不可通行网格");
Assert.AreEqual(0, (int)segmentValidation["invalidSampleCount"], $"{strategyName} 路径存在网格外采样点");
}
private static string BuildObstacleRouteJson()
{
return string.Format(
System.Globalization.CultureInfo.InvariantCulture,
@"{{""PathPlanningData"":{{""version"":""1.0"",""generator"":""integration-test"",""timestamp"":""2026-08-04T21:30:00"",""ProjectInfo"":{{""name"":""test"",""description"":""test"",""units"":""meters"",""coordinateSystem"":""Global""}},""Routes"":[{{""id"":""obstacle-route-{0}"",""name"":""{1}"",""description"":"""",""pathType"":""Ground"",""totalLength"":40.0,""objectLimits"":{{""maxLength"":0,""maxWidth"":0,""maxHeight"":0,""safetyMargin"":0}},""gridSize"":1.0,""liftHeight"":0.0,""created"":""2026-08-04T21:30:00"",""points"":[{{""id"":""ob-p1"",""name"":""start"",""type"":""StartPoint"",""index"":0,""x"":{2},""y"":{3},""z"":{4},""created"":""2026-08-04T21:30:00""}},{{""id"":""ob-p2"",""name"":""end"",""type"":""EndPoint"",""index"":1,""x"":{5},""y"":{6},""z"":{7},""created"":""2026-08-04T21:30:00""}}]}}]}}}}",
Guid.NewGuid().ToString("N"),
RouteName,
StartX, StartY, StartZ,
EndX, EndY, EndZ);
}
private static void SafeDelete(string directory)
{
if (!Directory.Exists(directory))
{
return;
}
try
{
Directory.Delete(directory, true);
}
catch
{
}
}
}
}