269 lines
8.2 KiB
C#
269 lines
8.2 KiB
C#
using System;
|
|
using MultiWheelC.Fleet;
|
|
using MyParking.Shared;
|
|
|
|
namespace MultiWheelC.Tests
|
|
{
|
|
internal static class FleetLayoutCaptureTests
|
|
{
|
|
private const double Tolerance = 1e-9;
|
|
|
|
public static void Run()
|
|
{
|
|
VerifySymmetricTailToTailLayout();
|
|
VerifyAsymmetricLayoutAndPoseReconstruction();
|
|
VerifyInputOrderDoesNotChangeResult();
|
|
VerifyEmptyInputIsRejected();
|
|
VerifyDuplicateVehicleIdIsRejected();
|
|
VerifyMissingLeaderIsRejected();
|
|
|
|
Console.WriteLine(
|
|
"FleetLayoutCapture布局建立测试通过。共6个场景。");
|
|
}
|
|
|
|
private static void VerifySymmetricTailToTailLayout()
|
|
{
|
|
var result = FleetLayoutCapture.Capture(
|
|
new[]
|
|
{
|
|
new FleetMemberPose(
|
|
1,
|
|
new Pose2D(-1.2, 0.0, 0.0)),
|
|
new FleetMemberPose(
|
|
2,
|
|
new Pose2D(1.2, 0.0, Math.PI))
|
|
},
|
|
leaderVehicleId: 1);
|
|
|
|
AssertPose(
|
|
result.FleetPoseInWorld,
|
|
Pose2D.Identity,
|
|
"对称双车中心");
|
|
AssertVehicleLayout(
|
|
result.Layout,
|
|
1,
|
|
new Pose2D(-1.2, 0.0, 0.0),
|
|
"对称双车主车布局");
|
|
AssertVehicleLayout(
|
|
result.Layout,
|
|
2,
|
|
new Pose2D(1.2, 0.0, Math.PI),
|
|
"对称双车从车布局");
|
|
}
|
|
|
|
private static void VerifyAsymmetricLayoutAndPoseReconstruction()
|
|
{
|
|
var members = new[]
|
|
{
|
|
new FleetMemberPose(
|
|
3,
|
|
new Pose2D(1.0, 1.0, 0.4)),
|
|
new FleetMemberPose(
|
|
1,
|
|
new Pose2D(4.0, 1.0, 0.4)),
|
|
new FleetMemberPose(
|
|
2,
|
|
new Pose2D(1.0, 4.0, -0.8))
|
|
};
|
|
var result = FleetLayoutCapture.Capture(
|
|
members,
|
|
leaderVehicleId: 1);
|
|
|
|
AssertPose(
|
|
result.FleetPoseInWorld,
|
|
new Pose2D(2.0, 2.0, 0.4),
|
|
"非对称三车中心");
|
|
|
|
for (var index = 0;
|
|
index < members.Length;
|
|
index++)
|
|
{
|
|
if (!result.Layout.TryGetVehicle(
|
|
members[index].VehicleId,
|
|
out var vehicleLayout))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"非对称布局缺少成员车。" +
|
|
members[index].VehicleId);
|
|
}
|
|
|
|
var reconstructedPoseInWorld =
|
|
FrameTransform2D.Compose(
|
|
result.FleetPoseInWorld,
|
|
vehicleLayout.PoseInFleet);
|
|
AssertPose(
|
|
reconstructedPoseInWorld,
|
|
members[index].PoseInWorld,
|
|
"非对称布局世界位姿还原");
|
|
}
|
|
}
|
|
|
|
private static void VerifyInputOrderDoesNotChangeResult()
|
|
{
|
|
var first = FleetLayoutCapture.Capture(
|
|
new[]
|
|
{
|
|
new FleetMemberPose(
|
|
1,
|
|
new Pose2D(2.0, 3.0, 0.6)),
|
|
new FleetMemberPose(
|
|
2,
|
|
new Pose2D(4.0, 5.0, -1.0))
|
|
},
|
|
leaderVehicleId: 1);
|
|
var second = FleetLayoutCapture.Capture(
|
|
new[]
|
|
{
|
|
new FleetMemberPose(
|
|
2,
|
|
new Pose2D(4.0, 5.0, -1.0)),
|
|
new FleetMemberPose(
|
|
1,
|
|
new Pose2D(2.0, 3.0, 0.6))
|
|
},
|
|
leaderVehicleId: 1);
|
|
|
|
AssertPose(
|
|
first.FleetPoseInWorld,
|
|
second.FleetPoseInWorld,
|
|
"输入顺序不变中心");
|
|
AssertSameLayout(first.Layout, second.Layout);
|
|
}
|
|
|
|
private static void VerifyEmptyInputIsRejected()
|
|
{
|
|
ExpectException<ArgumentException>(
|
|
() => FleetLayoutCapture.Capture(
|
|
Array.Empty<FleetMemberPose>(),
|
|
leaderVehicleId: 1),
|
|
"空成员集合");
|
|
}
|
|
|
|
private static void VerifyDuplicateVehicleIdIsRejected()
|
|
{
|
|
ExpectException<ArgumentException>(
|
|
() => FleetLayoutCapture.Capture(
|
|
new[]
|
|
{
|
|
new FleetMemberPose(
|
|
1,
|
|
Pose2D.Identity),
|
|
new FleetMemberPose(
|
|
1,
|
|
new Pose2D(1.0, 0.0, 0.0))
|
|
},
|
|
leaderVehicleId: 1),
|
|
"重复车号");
|
|
}
|
|
|
|
private static void VerifyMissingLeaderIsRejected()
|
|
{
|
|
ExpectException<ArgumentException>(
|
|
() => FleetLayoutCapture.Capture(
|
|
new[]
|
|
{
|
|
new FleetMemberPose(
|
|
2,
|
|
Pose2D.Identity)
|
|
},
|
|
leaderVehicleId: 1),
|
|
"缺少主车");
|
|
}
|
|
|
|
private static void AssertSameLayout(
|
|
FleetLayout first,
|
|
FleetLayout second)
|
|
{
|
|
if (first.VehicleCount != second.VehicleCount)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"输入顺序变化后成员数量发生变化。");
|
|
}
|
|
|
|
for (var index = 0;
|
|
index < first.Vehicles.Count;
|
|
index++)
|
|
{
|
|
var vehicle = first.Vehicles[index];
|
|
AssertVehicleLayout(
|
|
second,
|
|
vehicle.VehicleId,
|
|
vehicle.PoseInFleet,
|
|
"输入顺序不变布局");
|
|
}
|
|
}
|
|
|
|
private static void AssertVehicleLayout(
|
|
FleetLayout layout,
|
|
int vehicleId,
|
|
Pose2D expectedPoseInFleet,
|
|
string scenario)
|
|
{
|
|
if (!layout.TryGetVehicle(
|
|
vehicleId,
|
|
out var vehicle))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{scenario}缺少车辆{vehicleId}。");
|
|
}
|
|
|
|
AssertPose(
|
|
vehicle.PoseInFleet,
|
|
expectedPoseInFleet,
|
|
scenario);
|
|
}
|
|
|
|
private static void AssertPose(
|
|
Pose2D actual,
|
|
Pose2D expected,
|
|
string scenario)
|
|
{
|
|
AssertNear(
|
|
actual.XMeters,
|
|
expected.XMeters,
|
|
scenario + " X");
|
|
AssertNear(
|
|
actual.YMeters,
|
|
expected.YMeters,
|
|
scenario + " Y");
|
|
AssertNear(
|
|
AngleMath.NormalizeRadians(
|
|
actual.YawRadians -
|
|
expected.YawRadians),
|
|
0.0,
|
|
scenario + " Yaw");
|
|
}
|
|
|
|
private static void AssertNear(
|
|
double actual,
|
|
double expected,
|
|
string valueName)
|
|
{
|
|
if (Math.Abs(actual - expected) > Tolerance)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{valueName}错误:" +
|
|
$"actual={actual:F9}, expected={expected:F9}。");
|
|
}
|
|
}
|
|
|
|
private static void ExpectException<TException>(
|
|
Action action,
|
|
string scenario)
|
|
where TException : Exception
|
|
{
|
|
try
|
|
{
|
|
action();
|
|
}
|
|
catch (TException)
|
|
{
|
|
return;
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
$"{scenario}没有抛出{typeof(TException).Name}。");
|
|
}
|
|
}
|
|
}
|