- TestData 新增 CreateScenarioDrone/CreateScenarioUnit 等辅助方法 - 批量替换旧字段构造为 spec 引用 - 230/238 通过,8 个边缘测试待修
178 lines
7.5 KiB
C#
178 lines
7.5 KiB
C#
using System.Collections.Generic;
|
|
using CounterDrone.Core.Algorithms;
|
|
using CounterDrone.Core.Models;
|
|
using CounterDrone.Core.Simulation;
|
|
using Xunit;
|
|
|
|
namespace CounterDrone.Core.Tests
|
|
{
|
|
public class DroneEntityTests
|
|
{
|
|
private static DroneSpec CreateSpec(float speed = 120, float altitude = 300)
|
|
{
|
|
return new DroneSpec
|
|
{
|
|
Id = "test-drone",
|
|
DroneType = (int)DroneType.FixedWing,
|
|
PowerType = (int)PowerType.Piston,
|
|
TypicalSpeed = speed,
|
|
TypicalAltitude = altitude,
|
|
Wingspan = 2.5,
|
|
};
|
|
}
|
|
|
|
private List<Waypoint> CreateRoute(float fromX, float toX, float alt, float speed)
|
|
{
|
|
return new List<Waypoint>
|
|
{
|
|
new() { PosX = fromX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
|
new() { PosX = toX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
|
};
|
|
}
|
|
|
|
[Fact]
|
|
public void InitialPosition_IsFirstWaypoint()
|
|
{
|
|
var drone = new DroneEntity("d1", "g1", CreateSpec(),
|
|
CreateRoute(100, 500, 300, 120), 0, 50, 0, 0, FormationMode.Single);
|
|
Assert.Equal(100, drone.PosX, 1);
|
|
Assert.Equal(300, drone.PosY, 1);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_MovesTowardTarget()
|
|
{
|
|
var drone = new DroneEntity("d1", "g1", CreateSpec(),
|
|
CreateRoute(0, 1000, 300, 120), 0, 50, 0, 0, FormationMode.Single);
|
|
float prevX = drone.PosX;
|
|
drone.Update(1f);
|
|
Assert.True(drone.PosX > prevX);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_ReachesTarget_StatusChanges()
|
|
{
|
|
var drone = new DroneEntity("d1", "g1", CreateSpec(360),
|
|
CreateRoute(0, 100, 300, 360), 0, 50, 0, 0, FormationMode.Single);
|
|
drone.Update(2f);
|
|
Assert.Equal(DroneStatus.ReachedTarget, drone.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void Damage_ReducesHp()
|
|
{
|
|
var drone = new DroneEntity("d1", "g1", CreateSpec(),
|
|
CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
|
drone.ApplyDamage(0.4f);
|
|
Assert.Equal(0.6f, drone.Hp, 0.01f);
|
|
}
|
|
|
|
[Fact]
|
|
public void Damage_ExceedingHp_Destroys()
|
|
{
|
|
var drone = new DroneEntity("d1", "g1", CreateSpec(),
|
|
CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
|
drone.ApplyDamage(1.5f);
|
|
Assert.True(drone.Hp <= 0);
|
|
Assert.Equal(DroneStatus.Destroyed, drone.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void Formation_Lateral_ZOffsetsDiffer()
|
|
{
|
|
var route = CreateRoute(0, 100, 300, 60);
|
|
var d0 = new DroneEntity("d0", "g1", CreateSpec(), route, 0, 50, 0, 0, FormationMode.Formation);
|
|
var d1 = new DroneEntity("d1", "g1", CreateSpec(), route, 1, 50, 0, 0, FormationMode.Formation);
|
|
var d2 = new DroneEntity("d2", "g1", CreateSpec(), route, 2, 50, 0, 0, FormationMode.Formation);
|
|
Assert.NotEqual(d0.PosZ, d1.PosZ);
|
|
Assert.NotEqual(d1.PosZ, d2.PosZ);
|
|
Assert.NotEqual(d0.Route[0].PosZ, d1.Route[0].PosZ);
|
|
Assert.NotEqual(d0.Route[1].PosZ, d1.Route[1].PosZ);
|
|
}
|
|
|
|
[Fact]
|
|
public void Formation_Longitudinal_XOffsetsDiffer()
|
|
{
|
|
var route = CreateRoute(0, 100, 300, 60);
|
|
var d0 = new DroneEntity("d0", "g1", CreateSpec(), route, 0, 0, 0, 100, FormationMode.Formation);
|
|
var d1 = new DroneEntity("d1", "g1", CreateSpec(), route, 0, 0, 1, 100, FormationMode.Formation);
|
|
Assert.NotEqual(d0.PosX, d1.PosX);
|
|
}
|
|
|
|
[Fact]
|
|
public void Formation_Mixed_2x2()
|
|
{
|
|
var route = CreateRoute(0, 100, 300, 60);
|
|
var d0 = new DroneEntity("d0", "g1", CreateSpec(), route, 0, 50, 0, 100, FormationMode.Formation);
|
|
var d1 = new DroneEntity("d1", "g1", CreateSpec(), route, 1, 50, 0, 100, FormationMode.Formation);
|
|
var d2 = new DroneEntity("d2", "g1", CreateSpec(), route, 0, 50, 1, 100, FormationMode.Formation);
|
|
var d3 = new DroneEntity("d3", "g1", CreateSpec(), route, 1, 50, 1, 100, FormationMode.Formation);
|
|
Assert.NotEqual(d0.PosZ, d1.PosZ);
|
|
Assert.NotEqual(d0.PosX, d2.PosX);
|
|
}
|
|
|
|
[Fact]
|
|
public void Swarm_ProducesRandomOffsets()
|
|
{
|
|
var route = CreateRoute(0, 100, 300, 60);
|
|
var d0 = new DroneEntity("d0", "g1", CreateSpec(), route, 0, 30, 0, 0, FormationMode.Swarm);
|
|
var d1 = new DroneEntity("d1", "g1", CreateSpec(), route, 1, 30, 0, 0, FormationMode.Swarm);
|
|
Assert.True(d0.PosX != d1.PosX || d0.PosZ != d1.PosZ);
|
|
}
|
|
|
|
[Fact]
|
|
public void MultiWaypoint_LShape_MovesAlongBothSegments()
|
|
{
|
|
var route = new List<Waypoint>
|
|
{
|
|
new() { PosX = 0, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },
|
|
new() { PosX = 100, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },
|
|
new() { PosX = 100, PosY = 300, PosZ = 100, Altitude = 300, Speed = 60 },
|
|
};
|
|
var drone = new DroneEntity("d1", "g1", CreateSpec(speed: 60), route,
|
|
0, 0, 0, 0, FormationMode.Single);
|
|
drone.Update(50f / (60f / 3.6f));
|
|
Assert.Equal(50f, drone.PosX, 1);
|
|
Assert.Equal(0f, drone.PosZ, 1);
|
|
Assert.Equal(DroneStatus.Flying, drone.Status);
|
|
drone.Update(50f / (60f / 3.6f));
|
|
Assert.Equal(100f, drone.PosX, 1);
|
|
Assert.Equal(0f, drone.PosZ, 1);
|
|
Assert.Equal(DroneStatus.Flying, drone.Status);
|
|
drone.Update(50f / (60f / 3.6f));
|
|
Assert.Equal(100f, drone.PosX, 1);
|
|
Assert.Equal(50f, drone.PosZ, 1);
|
|
Assert.Equal(DroneStatus.Flying, drone.Status);
|
|
drone.Update(50f / (60f / 3.6f));
|
|
Assert.Equal(100f, drone.PosX, 1);
|
|
Assert.Equal(100f, drone.PosZ, 1);
|
|
Assert.Equal(DroneStatus.ReachedTarget, drone.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void MultiWaypoint_SubSegmentPositions_MatchGeometry()
|
|
{
|
|
var route = new List<Waypoint>
|
|
{
|
|
new() { PosX = 0, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },
|
|
new() { PosX = 50, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },
|
|
new() { PosX = 50, PosY = 300, PosZ = 120, Altitude = 300, Speed = 60 },
|
|
new() { PosX = 130, PosY = 300, PosZ = 120, Altitude = 300, Speed = 60 },
|
|
};
|
|
const float speed = 60f;
|
|
float speedMs = speed / 3.6f;
|
|
float[] sampleArcs = { 17f, 50f, 73f, 170f, 200f, 249f };
|
|
foreach (var arc in sampleArcs)
|
|
{
|
|
var drone = new DroneEntity("d", "g", CreateSpec(speed), route,
|
|
0, 0, 0, 0, FormationMode.Single);
|
|
drone.Update(arc / speedMs);
|
|
var (ex, ey, ez) = RouteGeometry.PositionAt(route, arc);
|
|
Assert.True(System.Math.Abs(drone.PosX - ex) < 0.5f, $"arc={arc}: X {drone.PosX} vs {ex}");
|
|
Assert.True(System.Math.Abs(drone.PosY - ey) < 0.5f, $"arc={arc}: Y {drone.PosY} vs {ey}");
|
|
Assert.True(System.Math.Abs(drone.PosZ - ez) < 0.5f, $"arc={arc}: Z {drone.PosZ} vs {ez}");
|
|
}
|
|
}
|
|
}
|
|
}
|