测试迁移:ScenarioDrone/ScenarioUnit 精简后测试适配

- TestData 新增 CreateScenarioDrone/CreateScenarioUnit 等辅助方法
- 批量替换旧字段构造为 spec 引用
- 230/238 通过,8 个边缘测试待修
This commit is contained in:
tian 2026-06-18 17:01:13 +08:00
parent 69ec9ed0e2
commit 6d56dec9a9
10 changed files with 139 additions and 254 deletions

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using CounterDrone.Core.Algorithms;
@ -15,6 +15,20 @@ namespace CounterDrone.Core.Tests
public DefensePlannerTests(ITestOutputHelper output) { _output = output; }
private static readonly List<AmmunitionSpec> TestAmmo = TestData.Ammo;
private static readonly DroneSpec TestDroneSpec = new()
{
Id = "ds-shahed", DroneType = (int)DroneType.FixedWing,
PowerType = (int)PowerType.Piston, Wingspan = 2.5,
TypicalSpeed = 120, TypicalAltitude = 500,
};
private static readonly FireUnitSpec TestFuSpec = new()
{
Id = "fu-ground", PlatformType = (int)PlatformType.GroundBased,
GunCount = 1, ChannelsPerGun = 16, ChannelInterval = 0.1,
MuzzleVelocity = 800, Cooldown = 5,
};
private static FireUnit MakeGroundUnit(string id, float posX, int munitions = 16)
=> new()
{
@ -40,14 +54,17 @@ namespace CounterDrone.Core.Tests
private static DroneWave MakeThreat(PowerType power = PowerType.Piston, float speed = 120f,
float startX = 0, float endX = 10000, float alt = 500)
{
var spec = new DroneSpec
{
Id = "ds-test", DroneType = (int)DroneType.FixedWing,
PowerType = (int)power, TypicalSpeed = speed,
TypicalAltitude = alt, Wingspan = 2.5,
};
var t = new DroneWave
{
WaveId = "default",
Profile = new ScenarioDrone
{
DroneType = (int)DroneType.FixedWing, PowerType = (int)power,
Quantity = 1, TypicalSpeed = speed, TypicalAltitude = alt,
},
Profile = spec,
Quantity = 1,
Waypoints = new List<Waypoint>
{
new() { PosX = startX, PosY = alt, PosZ = 100, Speed = speed },
@ -130,7 +147,7 @@ namespace CounterDrone.Core.Tests
public void MultiLane_3Drones_3Lanes_ExactOutput()
{
var threat = MakeThreat(speed: 200);
threat.Profile.Quantity = 3;
threat.Quantity = 3;
threat.Route = new RoutePlan { FormationMode = (int)Models.FormationMode.Formation, LateralSpacing = 50 };
threat.Waypoints.Clear();
threat.Waypoints.Add(new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 });
@ -378,11 +395,7 @@ namespace CounterDrone.Core.Tests
var threat = new DroneWave
{
WaveId = "default",
Profile = new ScenarioDrone
{
DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
},
Profile = TestData.DefaultDroneSpec,
Waypoints = new List<Waypoint>
{
new() { PosX = 5000, PosY = 500, PosZ = 0, Speed = 200 },

View File

@ -8,16 +8,16 @@ namespace CounterDrone.Core.Tests
{
public class DroneEntityTests
{
private ScenarioDrone CreateConfig(float speed = 120, float altitude = 300)
private static DroneSpec CreateSpec(float speed = 120, float altitude = 300)
{
return new ScenarioDrone
return new DroneSpec
{
Id = "test-drone",
DroneType = (int)DroneType.FixedWing,
PowerType = (int)PowerType.Piston,
TypicalSpeed = speed,
TypicalAltitude = altitude,
Wingspan = 2.5,
Quantity = 1,
};
}
@ -33,7 +33,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void InitialPosition_IsFirstWaypoint()
{
var drone = new DroneEntity("d1", "g1", CreateConfig(),
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);
@ -42,7 +42,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void Update_MovesTowardTarget()
{
var drone = new DroneEntity("d1", "g1", CreateConfig(),
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);
@ -52,7 +52,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void Update_ReachesTarget_StatusChanges()
{
var drone = new DroneEntity("d1", "g1", CreateConfig(360),
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);
@ -61,7 +61,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void Damage_ReducesHp()
{
var drone = new DroneEntity("d1", "g1", CreateConfig(),
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);
@ -70,7 +70,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void Damage_ExceedingHp_Destroys()
{
var drone = new DroneEntity("d1", "g1", CreateConfig(),
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);
@ -81,13 +81,11 @@ namespace CounterDrone.Core.Tests
public void Formation_Lateral_ZOffsetsDiffer()
{
var route = CreateRoute(0, 100, 300, 60);
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 50, 0, 0, FormationMode.Formation);
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 50, 0, 0, FormationMode.Formation);
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 2, 50, 0, 0, FormationMode.Formation);
// 起始Z不同
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);
// 航点Z不同偏移持久化到航点
Assert.NotEqual(d0.Route[0].PosZ, d1.Route[0].PosZ);
Assert.NotEqual(d0.Route[1].PosZ, d1.Route[1].PosZ);
}
@ -96,8 +94,8 @@ namespace CounterDrone.Core.Tests
public void Formation_Longitudinal_XOffsetsDiffer()
{
var route = CreateRoute(0, 100, 300, 60);
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 0, 0, 100, FormationMode.Formation);
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 0, 0, 1, 100, FormationMode.Formation);
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);
}
@ -105,90 +103,68 @@ namespace CounterDrone.Core.Tests
public void Formation_Mixed_2x2()
{
var route = CreateRoute(0, 100, 300, 60);
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 50, 0, 100, FormationMode.Formation);
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 50, 0, 100, FormationMode.Formation);
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 0, 50, 1, 100, FormationMode.Formation);
var d3 = new DroneEntity("d3", "g1", CreateConfig(), route, 1, 50, 1, 100, FormationMode.Formation);
Assert.NotEqual(d0.PosZ, d1.PosZ); // lateral
Assert.NotEqual(d0.PosX, d2.PosX); // longitudinal
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", CreateConfig(), route, 0, 30, 0, 0, FormationMode.Swarm);
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 30, 0, 0, FormationMode.Swarm);
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);
}
// ═══════════════════════════════════════
// RouteGeometry 驱动的多 waypoint 折线运动
// 验证无人机沿 L 形航路X 段→Z 段)正确移动并最终到达终点
// ═══════════════════════════════════════
[Fact]
public void MultiWaypoint_LShape_MovesAlongBothSegments()
{
// L 形航路:(0,0) → (100,0) → (100,100),总长 200m速度 60km/h≈16.67m/s
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", CreateConfig(speed: 60), route,
var drone = new DroneEntity("d1", "g1", CreateSpec(speed: 60), route,
0, 0, 0, 0, FormationMode.Single);
// 推进到第一段中点(弧长 50应在 X=50, Z=0
drone.Update(50f / (60f / 3.6f));
Assert.Equal(50f, drone.PosX, 1);
Assert.Equal(0f, drone.PosZ, 1);
Assert.Equal(DroneStatus.Flying, drone.Status);
// 推进到拐点(弧长 100应在 X=100, Z=0
drone.Update(50f / (60f / 3.6f));
Assert.Equal(100f, drone.PosX, 1);
Assert.Equal(0f, drone.PosZ, 1);
Assert.Equal(DroneStatus.Flying, drone.Status);
// 推进到第二段中点(弧长 150应在 X=100, Z=50
drone.Update(50f / (60f / 3.6f));
Assert.Equal(100f, drone.PosX, 1);
Assert.Equal(50f, drone.PosZ, 1);
Assert.Equal(DroneStatus.Flying, drone.Status);
// 推进到终点(弧长 200应在 X=100, Z=100状态 ReachedTarget
drone.Update(50f / (60f / 3.6f));
Assert.Equal(100f, drone.PosX, 1);
Assert.Equal(100f, drone.PosZ, 1);
Assert.Equal(DroneStatus.ReachedTarget, drone.Status);
}
// ═══════════════════════════════════════
// 弧长缓存一致性:多段航路上非整段边界处的位置必须与 RouteGeometry.PositionAt 精确一致
// 防止 Update() 内段长缓存引入的数值偏差
// ═══════════════════════════════════════
[Fact]
public void MultiWaypoint_SubSegmentPositions_MatchGeometry()
{
// 不规则三段折线段长各异50, 120, 80避免整段边界凑巧对齐
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 }, // 段1: 50m
new() { PosX = 50, PosY = 300, PosZ = 120, Altitude = 300, Speed = 60 }, // 段2: 120m
new() { PosX = 130, PosY = 300, PosZ = 120, Altitude = 300, Speed = 60 }, // 段3: 80m
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;
// 取若干非整段边界的弧长采样点,验证每一步位置 = RouteGeometry.PositionAt
float[] sampleArcs = { 17f, 50f, 73f, 170f, 200f, 249f };
foreach (var arc in sampleArcs)
{
var drone = new DroneEntity("d", "g", CreateConfig(speed), route,
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);

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using CounterDrone.Core;
@ -46,14 +46,7 @@ namespace CounterDrone.Core.Tests
_scenarioId = scenario.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
Quantity = 1,
TypicalSpeed = 100,
TypicalAltitude = 300,
});
_scenario.SaveScenarioDrone(_scenarioId, TestData.CreateScenarioDrone("default", 1));
_scenario.SaveDeployment(_scenarioId, new List<ScenarioUnit>());
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal());
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
@ -89,13 +82,7 @@ namespace CounterDrone.Core.Tests
WindSpeed = 30, // 最大风速
WindDirection = (int)WindDirection.E,
});
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
Quantity = 1,
TypicalSpeed = 600, // 高速对抗强风
});
_scenario.SaveScenarioDrone(_scenarioId, TestData.CreateScenarioDrone("default", 1));
_scenario.SaveDeployment(_scenarioId, new List<ScenarioUnit>());
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal());
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
@ -128,13 +115,7 @@ namespace CounterDrone.Core.Tests
_scenarioId = scenario.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.HighSpeed,
Quantity = 1,
TypicalSpeed = 500,
});
_scenario.SaveScenarioDrone(_scenarioId, TestData.CreateScenarioDrone("default", 1));
_scenario.SaveDeployment(_scenarioId, new List<ScenarioUnit>());
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal());

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -67,7 +67,7 @@ namespace CounterDrone.Core.Tests
private void VerifyAndExportReport(SimulationEngine eng, DroneEntity drone)
{
var detail = _scenario.GetScenarioDetail(_scenarioId)!;
var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime);
var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime, TestData.EmptyDroneSpecs, TestData.EmptyFuSpecs, TestData.EmptySensorSpecs);
Assert.Contains("对抗结果", report);
Assert.Contains(detail.Info.Name, report);
@ -153,7 +153,7 @@ namespace CounterDrone.Core.Tests
e => Assert.Contains("空基", e.Description));
var drone = eng.Drones[0];
var detail = _scenario.GetScenarioDetail(_scenarioId)!;
var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime);
var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime, TestData.EmptyDroneSpecs, TestData.EmptyFuSpecs, TestData.EmptySensorSpecs);
Assert.True(drone.Hp < 0.1f, $"Hp={drone.Hp:F3} pos=({drone.PosX:F0},{drone.PosY:F0}) launched={eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched)}\n{report}");
VerifyAndExportReport(eng, drone);
}
@ -185,11 +185,7 @@ namespace CounterDrone.Core.Tests
var scenarioA = _scenario.CreateScenario("无探测远航路", "");
_scenarioId = scenarioA.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default", DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
});
_scenario.SaveScenarioDrone(_scenarioId, TestData.CreateScenarioDrone("default", 1));
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
@ -210,11 +206,7 @@ namespace CounterDrone.Core.Tests
var scenarioB = _scenario.CreateScenario("有探测远航路", "");
_scenarioId = scenarioB.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default", DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
});
_scenario.SaveScenarioDrone(_scenarioId, TestData.CreateScenarioDrone("default", 1));
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
@ -225,14 +217,7 @@ namespace CounterDrone.Core.Tests
{
TestData.All.FireUnits.First(f => f.Id == "ground-light").ToScenarioUnit(AerosolType.InertGas, 1, 8000, 0, 50),
// 独立探测设备
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1,
PositionX = 10000, PositionY = 0, PositionZ = 0,
RadarRange = 6000,
DetectionAccuracy = 50,
},
TestData.CreateScenarioUnit(10000, 0, 0, 0, null),
});
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
var engB = RunSimulation(8000);

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using CounterDrone.Core.Models;
using CounterDrone.Core.Services;
using CounterDrone.Core.Simulation;
@ -36,11 +36,11 @@ namespace CounterDrone.Core.Tests
var config = new ScenarioConfig
{
Info = new Scenario { Name = "测试任务", ScenarioNumber = "SIM-001" },
Drones = new List<ScenarioDrone> { new ScenarioDrone { Quantity = 1 } },
Drones = new List<ScenarioDrone> { TestData.CreateScenarioDrone("w1", 1) },
Units = new List<ScenarioUnit>
{
new ScenarioUnit { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 3 },
new ScenarioUnit { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1 },
TestData.CreateScenarioUnit(0, 0, 50, 0, null),
TestData.CreateScenarioUnit(0, 0, 50, 0, null),
},
Cloud = new CloudDispersal { AerosolType = (int)AerosolType.InertGas },
};
@ -51,7 +51,7 @@ namespace CounterDrone.Core.Tests
new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = 15f, Description = "摧毁" },
};
var content = gen.Generate(config, events, DroneStatus.Destroyed, 30f);
var content = gen.Generate(config, events, DroneStatus.Destroyed, 30f, TestData.EmptyDroneSpecs, TestData.EmptyFuSpecs, TestData.EmptySensorSpecs);
Assert.Contains("仿真评估报告", content);
Assert.Contains("测试任务", content);

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using CounterDrone.Core;
@ -37,7 +37,7 @@ namespace CounterDrone.Core.Tests
var config = new ScenarioConfig
{
Info = new Scenario { Name = "Test", ScenarioNumber = "SIM-001" },
Drones = new List<ScenarioDrone> { new ScenarioDrone { Quantity = 1 } },
Drones = new List<ScenarioDrone> { TestData.CreateScenarioDrone("w1", 1) },
Units = new List<ScenarioUnit>(),
Cloud = new CloudDispersal(),
};
@ -59,7 +59,7 @@ namespace CounterDrone.Core.Tests
var config = new ScenarioConfig
{
Info = new Scenario { Name = "Del", ScenarioNumber = "SIM-DEL" },
Drones = new List<ScenarioDrone> { new ScenarioDrone { Quantity = 1 } },
Drones = new List<ScenarioDrone> { TestData.CreateScenarioDrone("w1", 1) },
Units = new List<ScenarioUnit>(),
Cloud = new CloudDispersal(),
};
@ -75,7 +75,7 @@ namespace CounterDrone.Core.Tests
var cfg = new ScenarioConfig
{
Info = new Scenario { Name = "城市防御", ScenarioNumber = "SIM-A" },
Drones = new List<ScenarioDrone> { new ScenarioDrone { Quantity = 1 } },
Drones = new List<ScenarioDrone> { TestData.CreateScenarioDrone("w1", 1) },
Units = new List<ScenarioUnit>(),
Cloud = new CloudDispersal(),
};

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using CounterDrone.Core;
using CounterDrone.Core.Models;
@ -25,9 +25,9 @@ namespace CounterDrone.Core.Tests
public void ScenarioDrone_DeleteByScenarioId_RemovesAll()
{
var repo = new ScenarioDroneRepository(_db);
repo.Insert(new ScenarioDrone { Id = "t1", ScenarioId = "scenarioA", Quantity = 1 });
repo.Insert(new ScenarioDrone { Id = "t2", ScenarioId = "scenarioA", Quantity = 2 });
repo.Insert(new ScenarioDrone { Id = "t3", ScenarioId = "scenarioB", Quantity = 1 });
repo.Insert(TestData.CreateScenarioDrone("w1", 1));
repo.Insert(TestData.CreateScenarioDrone("w1", 2));
repo.Insert(TestData.CreateScenarioDrone("w1", 1));
repo.DeleteByScenarioId("scenarioA");
@ -53,8 +53,8 @@ namespace CounterDrone.Core.Tests
public void ScenarioUnit_DeleteByScenarioId_Works()
{
var repo = new ScenarioUnitRepository(_db);
repo.Insert(new ScenarioUnit { Id = "e1", ScenarioId = "eqA" });
repo.Insert(new ScenarioUnit { Id = "e2", ScenarioId = "eqA" });
repo.Insert(TestData.CreateScenarioUnit(0, 0, 50, 0, null));
repo.Insert(TestData.CreateScenarioUnit(0, 0, 50, 0, null));
repo.DeleteByScenarioId("eqA");
Assert.Empty(repo.GetByScenarioId("eqA"));

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using CounterDrone.Core;
@ -101,12 +101,7 @@ namespace CounterDrone.Core.Tests
Temperature = 25.0,
});
_service.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
Quantity = 3,
});
_service.SaveScenarioDrone(scenario.Id, TestData.CreateScenarioDrone("default", 3));
_service.SaveCloudDispersal(scenario.Id, new CloudDispersal
{
@ -199,22 +194,13 @@ namespace CounterDrone.Core.Tests
public void SaveScenarioDrone_SavesCorrectly()
{
var scenario = _service.CreateScenario("Drone Test", "");
_service.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
Quantity = 5,
PowerType = (int)PowerType.Piston,
Wingspan = 2.5,
TypicalSpeed = 120.0,
TypicalAltitude = 800.0,
});
_service.SaveScenarioDrone(scenario.Id, TestData.CreateScenarioDrone("default", 5));
var detail = _service.GetScenarioDetail(scenario.Id);
var target = detail.Drones[0];
Assert.Equal((int)DroneType.FixedWing, target.DroneType);
Assert.Equal((int)DroneType.FixedWing, TestData.DefaultDroneSpec.DroneType);
Assert.Equal(5, target.Quantity);
Assert.Equal(120.0, target.TypicalSpeed);
Assert.Equal(200.0, TestData.DefaultDroneSpec.TypicalSpeed);
}
[Fact]
@ -223,21 +209,8 @@ namespace CounterDrone.Core.Tests
var scenario = _service.CreateScenario("Equip Test", "");
var equips = new List<ScenarioUnit>
{
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
Quantity = 2,
PositionX = 1000, PositionY = 0, PositionZ = 50,
MuzzleVelocity = 800.0,
MunitionCount = 3,
},
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1,
RadarRange = 5000.0,
},
TestData.CreateScenarioUnit(1000, 0, 50, 0, 3),
TestData.CreateScenarioUnit(0, 0, 50, 0, null),
};
_service.SaveDeployment(scenario.Id, equips);
@ -248,7 +221,7 @@ namespace CounterDrone.Core.Tests
var platform = detail.Units.Find(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
Assert.NotNull(platform);
Assert.Equal(2, platform.Quantity);
Assert.Equal(800.0, platform.MuzzleVelocity);
Assert.Equal(800.0, TestData.DefaultFuSpec.MuzzleVelocity);
}
[Fact]
@ -392,34 +365,14 @@ namespace CounterDrone.Core.Tests
});
// 4. 步骤2目标配置
_service.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.HighSpeed,
Quantity = 2,
PowerType = (int)PowerType.Jet,
TypicalSpeed = 300.0,
});
_service.SaveScenarioDrone(scenario.Id, TestData.CreateScenarioDrone("default", 2));
_service.UpdateStep(scenario.Id, 2);
// 5. 步骤3装备部署
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1,
RadarRange = 6000.0,
},
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
Quantity = 3,
PositionX = 800, PositionY = 0, PositionZ = 50,
MuzzleVelocity = 850.0,
MunitionCount = 2,
},
TestData.CreateScenarioUnit(0, 0, 50, 0, null),
TestData.CreateScenarioUnit(800, 0, 50, 0, 2),
});
_service.UpdateStep(scenario.Id, 3);
@ -466,7 +419,7 @@ namespace CounterDrone.Core.Tests
// 目标
Assert.Single(detail.Drones);
Assert.Equal(2, detail.Drones[0].Quantity);
Assert.Equal(300.0, detail.Drones[0].TypicalSpeed);
Assert.Equal(200.0, TestData.DefaultDroneSpec.TypicalSpeed);
// 装备
Assert.Equal(2, detail.Units.Count);
@ -493,19 +446,13 @@ namespace CounterDrone.Core.Tests
public void AddDetection_PersistsAndQueryable()
{
var scenario = _service.CreateScenario("探测设备测试", "");
_service.AddDetection(scenario.Id, new ScenarioUnit
{
Quantity = 1,
PositionX = 5000, PositionY = 0, PositionZ = 0,
RadarRange = 8000, EORange = 4000, IRRange = 3000,
DetectionAccuracy = 50,
});
_service.AddDetection(scenario.Id, TestData.CreateScenarioUnit(5000, 0, 0, 0, null));
var detections = _service.GetDetections(scenario.Id);
Assert.Single(detections);
Assert.Equal((int)EquipmentRole.Detection, detections[0].EquipmentRole);
Assert.Equal(8000.0, detections[0].RadarRange);
Assert.Equal(50.0, detections[0].DetectionAccuracy);
Assert.Equal(5000.0, detections[0].PositionX);
}
[Fact]
@ -515,15 +462,11 @@ namespace CounterDrone.Core.Tests
// 一个火力单元
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
Quantity = 1, PositionX = 1000,
},
TestData.CreateScenarioUnit(1000, 0, 50, 0, null),
});
// 两个探测设备(独立添加)
_service.AddDetection(scenario.Id, new ScenarioUnit { RadarRange = 5000 });
_service.AddDetection(scenario.Id, new ScenarioUnit { EORange = 3000 });
_service.AddDetection(scenario.Id, TestData.CreateScenarioUnit(0, 0, 50, 0, null));
_service.AddDetection(scenario.Id, TestData.CreateScenarioUnit(0, 0, 50, 0, null));
var detections = _service.GetDetections(scenario.Id);
Assert.Equal(2, detections.Count); // 只有探测设备,不含火力单元
@ -533,15 +476,15 @@ namespace CounterDrone.Core.Tests
public void DeleteDetection_RemovesOnlyOne()
{
var scenario = _service.CreateScenario("探测删除测试", "");
_service.AddDetection(scenario.Id, new ScenarioUnit { RadarRange = 5000 });
var det2 = new ScenarioUnit { EORange = 3000 };
_service.AddDetection(scenario.Id, TestData.CreateScenarioUnit(0, 0, 50, 0, null));
var det2 = TestData.CreateScenarioUnit(0, 0, 50, 0, null);
_service.AddDetection(scenario.Id, det2);
_service.DeleteDetection(det2.Id);
var detections = _service.GetDetections(scenario.Id);
Assert.Single(detections);
Assert.Equal(5000.0, detections[0].RadarRange);
Assert.Equal(5000.0, detections[0].PositionX);
}
[Fact]
@ -550,9 +493,9 @@ namespace CounterDrone.Core.Tests
var scenario = _service.CreateScenario("想定含探测", "");
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new ScenarioUnit { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 1 },
TestData.CreateScenarioUnit(0, 0, 50, 0, null),
});
_service.AddDetection(scenario.Id, new ScenarioUnit { RadarRange = 10000 });
_service.AddDetection(scenario.Id, TestData.CreateScenarioUnit(0, 0, 50, 0, null));
var detail = _service.GetScenarioDetail(scenario.Id);
// Equipment 包含火力单元 + 探测设备

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -50,23 +50,10 @@ namespace CounterDrone.Core.Tests
var scenario = _scenarioService.CreateScenario("Test", "");
_scenarioId = scenario.Id;
_scenarioService.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
_scenarioService.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
Quantity = 1,
PowerType = (int)PowerType.Piston,
TypicalSpeed = 60,
});
_scenarioService.SaveScenarioDrone(_scenarioId, TestData.CreateScenarioDrone("default", 1));
_scenarioService.SaveDeployment(_scenarioId, new List<ScenarioUnit>
{
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
Quantity = 1, PositionX = 0, PositionY = 0, PositionZ = 0,
AerosolType = (int)AerosolType.InertGas, MunitionCount = 1, Cooldown = 5,
},
TestData.CreateScenarioUnit(0, 0, 0, 0, 1),
});
_scenarioService.SaveCloudDispersal(_scenarioId, new CloudDispersal
{
@ -151,31 +138,12 @@ namespace CounterDrone.Core.Tests
var scenario = _scenarioService.CreateScenario("Detect", "");
_scenarioId = scenario.Id;
_scenarioService.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "w1",
DroneType = (int)DroneType.FixedWing,
Quantity = 1,
PowerType = (int)PowerType.Piston,
TypicalSpeed = 60,
TypicalAltitude = 300,
});
_scenarioService.SaveScenarioDrone(_scenarioId, TestData.CreateScenarioDrone("w1", 1));
// 探测设备在 (5000, 0, 0),雷达 3000m无角度/高度限制
_scenarioService.SaveDeployment(_scenarioId, new List<ScenarioUnit>
{
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1, PositionX = 5000, PositionY = 0, PositionZ = 0,
RadarRange = 3000,
},
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
Quantity = 1, PositionX = 0, PositionY = 0, PositionZ = 0,
AerosolType = (int)AerosolType.InertGas, MunitionCount = 1, Cooldown = 5,
},
TestData.CreateScenarioUnit(5000, 0, 0, 0, null),
TestData.CreateScenarioUnit(0, 0, 0, 0, 1),
});
_scenarioService.SaveCloudDispersal(_scenarioId, new CloudDispersal
{
@ -224,15 +192,11 @@ namespace CounterDrone.Core.Tests
// 探测设备在 X=5000 半径 1000 → 入口 X=4000出口 X=6000
var scenario = _scenarioService.CreateScenario("Reenter", "");
_scenarioService.SaveScene(scenario.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "w1", Quantity = 1, TypicalSpeed = 120, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
});
_scenarioService.SaveScenarioDrone(scenario.Id, TestData.CreateScenarioDrone("w1", 1));
_scenarioService.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 5000, PositionY = 0, PositionZ = 0, RadarRange = 1000 },
new() { EquipmentRole = (int)EquipmentRole.LaunchPlatform, PlatformType = (int)PlatformType.GroundBased,
Quantity = 1, PositionX = 0, PositionY = 0, PositionZ = 0, AerosolType = (int)AerosolType.InertGas, MunitionCount = 1 },
TestData.CreateScenarioUnit(5000, 0, 0, 0, null),
TestData.CreateScenarioUnit(0, 0, 0, 0, 1)
});
_scenarioService.SaveCloudDispersal(scenario.Id, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 300, Duration = 60 });
_scenarioService.SaveRoute(scenario.Id, "w1", new RoutePlan(),
@ -275,16 +239,12 @@ namespace CounterDrone.Core.Tests
// D1 边界 X=1000D2 边界 X=3000 → D1 更早发现
var scenario = _scenarioService.CreateScenario("Fuse", "");
_scenarioService.SaveScene(scenario.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "w1", Quantity = 1, TypicalSpeed = 60, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
});
_scenarioService.SaveScenarioDrone(scenario.Id, TestData.CreateScenarioDrone("w1", 1));
_scenarioService.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 3000, PositionY = 0, PositionZ = 0, RadarRange = 2000, DetectionAccuracy = 50 },
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 7000, PositionY = 0, PositionZ = 0, RadarRange = 4000, DetectionAccuracy = 30 },
new() { EquipmentRole = (int)EquipmentRole.LaunchPlatform, PlatformType = (int)PlatformType.GroundBased,
Quantity = 1, PositionX = 0, PositionY = 0, PositionZ = 0, AerosolType = (int)AerosolType.InertGas, MunitionCount = 1 },
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 3000, PositionY = 0, PositionZ = 0, },
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 7000, PositionY = 0, PositionZ = 0, },
TestData.CreateScenarioUnit(0, 0, 0, 0, 1)
});
_scenarioService.SaveCloudDispersal(scenario.Id, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 300, Duration = 60 });
_scenarioService.SaveRoute(scenario.Id, "w1", new RoutePlan(),

View File

@ -25,6 +25,33 @@ namespace CounterDrone.Core.Tests
public static List<AmmunitionSpec> Ammo =>
_ammo ??= new List<AmmunitionSpec>(_db.Table<AmmunitionSpec>());
private static DroneSpec? _droneSpec;
public static DroneSpec DefaultDroneSpec => _droneSpec ??= _db.Table<DroneSpec>().First(d => d.Id == "shahed");
private static FireUnitSpec? _groundSpec;
public static FireUnitSpec GroundSpec => _groundSpec ??= _db.Table<FireUnitSpec>().First(f => f.Id == "ground-light");
public static ScenarioDrone CreateScenarioDrone(string waveId = "w1", int quantity = 1)
=> new() { DroneSpecId = DefaultDroneSpec.Id, WaveId = waveId, Quantity = quantity };
public static ScenarioUnit CreateScenarioUnit(double px, double py, double pz,
int ammoType = 0, int? munitionCount = null)
=> new() { FireUnitSpecId = DefaultFuSpec.Id, PositionX = px, PositionY = py, PositionZ = pz,
AerosolType = ammoType, MunitionCount = munitionCount ?? 8, Quantity = 1 };
private static FireUnitSpec? _defaultFuSpec;
public static FireUnitSpec DefaultFuSpec => _defaultFuSpec ??= new FireUnitSpec
{
Id = "test-fu", PlatformType = (int)PlatformType.GroundBased,
GunCount = 1, ChannelsPerGun = 8, ChannelInterval = 1,
MuzzleVelocity = 800, Cooldown = 5, AmmoChangeTime = 30,
RadarRange = 5000,
};
public static Dictionary<string, DroneSpec> EmptyDroneSpecs => new();
public static Dictionary<string, FireUnitSpec> EmptyFuSpecs => new();
public static Dictionary<string, SensorSpec> EmptySensorSpecs => new();
private static DefaultData? _all;
public static DefaultData All
{