Create fluent interface for building test cases
This commit is contained in:
parent
b0a8100641
commit
9e1782c401
422
MultiversalDiplomacyTests/TestCaseBuilder.cs
Normal file
422
MultiversalDiplomacyTests/TestCaseBuilder.cs
Normal file
@ -0,0 +1,422 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
using MultiversalDiplomacy.Model;
|
||||
using MultiversalDiplomacy.Orders;
|
||||
|
||||
namespace MultiversalDiplomacyTests;
|
||||
|
||||
/// <summary>
|
||||
/// A fluent interface for defining adjudication test cases.
|
||||
/// </summary>
|
||||
public class TestCaseBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Context for defining orders given by a power.
|
||||
/// </summary>
|
||||
public interface IPowerContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the context for defining the orders for another power.
|
||||
/// </summary>
|
||||
public IPowerContext this[string powerName] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Define an order for an army in a province.
|
||||
/// </summary>
|
||||
public IUnitContext Army(string provinceName);
|
||||
|
||||
/// <summary>
|
||||
/// Define an order for a fleet in a province, optionally on a specific coast.
|
||||
/// </summary>
|
||||
public IUnitContext Fleet(string provinceName, string? coast = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context for defining an order given to a unit.
|
||||
/// </summary>
|
||||
public interface IUnitContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensure the unit exists, but don't create an order for it.
|
||||
/// </summary>
|
||||
public IPowerContext Exists();
|
||||
|
||||
/// <summary>
|
||||
/// Give the unit a hold order.
|
||||
/// </summary>
|
||||
public IPowerContext Holds();
|
||||
|
||||
/// <summary>
|
||||
/// Give the unit a move order.
|
||||
/// </summary>
|
||||
public IPowerContext MovesTo(string provinceName, string? coast = null);
|
||||
|
||||
/// <summary>
|
||||
/// Give the unit a convoy order.
|
||||
/// </summary>
|
||||
public IConvoyContext Convoys { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Give the unit a support order.
|
||||
/// </summary>
|
||||
public ISupportContext Supports { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context for defining a convoy order.
|
||||
/// </summary>
|
||||
public interface IConvoyContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Make the convoy order target an army.
|
||||
/// </summary>
|
||||
public IConvoyDestinationContext Army(string provinceName, string? powerName = null);
|
||||
|
||||
/// <summary>
|
||||
/// Make the convoy order target a fleet.
|
||||
/// </summary>
|
||||
public IConvoyDestinationContext Fleet(
|
||||
string provinceName,
|
||||
string? coast = null,
|
||||
string? powerName = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context for defining the destination of a convoy order.
|
||||
/// </summary>
|
||||
public interface IConvoyDestinationContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Define the destination of the convoy order.
|
||||
/// </summary>
|
||||
public IPowerContext To(string provinceName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context for defining a support order.
|
||||
/// </summary>
|
||||
public interface ISupportContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Make the support order target an army.
|
||||
/// </summary>
|
||||
public ISupportTypeContext Army(string provinceName, string? powerName = null);
|
||||
|
||||
/// <summary>
|
||||
/// Make the support order target a fleet.
|
||||
/// </summary>
|
||||
public ISupportTypeContext Fleet(
|
||||
string provinceName,
|
||||
string? coast = null,
|
||||
string? powerName = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context for defining the type of support order.
|
||||
/// </summary>
|
||||
public interface ISupportTypeContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Give the unit an order to support the target's hold order.
|
||||
/// </summary>
|
||||
public IPowerContext Hold();
|
||||
|
||||
/// <summary>
|
||||
/// Give the unit an order to support the target's move order.
|
||||
/// </summary>
|
||||
public IPowerContext MoveTo(string provinceName, string? coast = null);
|
||||
}
|
||||
|
||||
public World World { get; private set; }
|
||||
public ReadOnlyCollection<Order> Orders { get; }
|
||||
private List<Order> OrderList;
|
||||
private Season Season;
|
||||
|
||||
/// <summary>
|
||||
/// Create a test case builder that will operate on a world.
|
||||
/// </summary>
|
||||
public TestCaseBuilder(World world, Season? season = null)
|
||||
{
|
||||
this.World = world;
|
||||
this.OrderList = new List<Order>();
|
||||
this.Orders = new(this.OrderList);
|
||||
this.Season = season ?? this.World.Seasons.First();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the context for defining the orders for a power.
|
||||
/// </summary>
|
||||
public IPowerContext this[string powerName]
|
||||
{
|
||||
get
|
||||
{
|
||||
Power power = this.World.GetPower(powerName);
|
||||
return new PowerContext(this, power);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a unit matching a description. If no such unit exists, one is created and added to the
|
||||
/// <see cref="World"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">
|
||||
/// The unit type to create if the unit does not exist.
|
||||
/// Per DATC 4.C.1-2, mismatching unit designations should not invalidate an order, which
|
||||
/// effectively makes the designations superfluous. To support this, the test case builder
|
||||
/// returns a unit that matches the power, location, and season even if the unit found is not
|
||||
/// of this type.
|
||||
/// </param>
|
||||
private Unit GetOrBuildUnit(
|
||||
Power power,
|
||||
Location location,
|
||||
Season season,
|
||||
UnitType type)
|
||||
{
|
||||
foreach (Unit unit in this.World.Units)
|
||||
{
|
||||
if (unit.Power == power
|
||||
&& unit.Location == location
|
||||
&& unit.Season == season)
|
||||
{
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
|
||||
// Not found
|
||||
Unit newUnit = Unit.Build(location, season, power, type);
|
||||
this.World = this.World.WithUnits(this.World.Units.Append(newUnit));
|
||||
return newUnit;
|
||||
}
|
||||
|
||||
private class PowerContext : IPowerContext
|
||||
{
|
||||
public TestCaseBuilder Builder;
|
||||
public Power Power;
|
||||
|
||||
public PowerContext(TestCaseBuilder Builder, Power Power)
|
||||
{
|
||||
this.Builder = Builder;
|
||||
this.Power = Power;
|
||||
}
|
||||
|
||||
public IPowerContext this[string powerName]
|
||||
=> this.Builder[powerName];
|
||||
|
||||
public IUnitContext Army(string provinceName)
|
||||
{
|
||||
Location location = this.Builder.World.GetLand(provinceName);
|
||||
Unit unit = this.Builder.GetOrBuildUnit(
|
||||
this.Power, location, this.Builder.Season, UnitType.Army);
|
||||
return new UnitContext(this, unit);
|
||||
}
|
||||
|
||||
public IUnitContext Fleet(string provinceName, string? coast = null)
|
||||
{
|
||||
Location location = this.Builder.World.GetWater(provinceName, coast);
|
||||
Unit unit = this.Builder.GetOrBuildUnit(
|
||||
this.Power, location, this.Builder.Season, UnitType.Fleet);
|
||||
return new UnitContext(this, unit);
|
||||
}
|
||||
}
|
||||
|
||||
private class UnitContext : IUnitContext
|
||||
{
|
||||
public TestCaseBuilder Builder;
|
||||
public PowerContext PowerContext;
|
||||
public Unit Unit;
|
||||
|
||||
public UnitContext(PowerContext powerContext, Unit unit)
|
||||
{
|
||||
this.Builder = powerContext.Builder;
|
||||
this.PowerContext = powerContext;
|
||||
this.Unit = unit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declare that a unit exists without giving it an order.
|
||||
/// </summary>
|
||||
public IPowerContext Exists()
|
||||
=> this.PowerContext;
|
||||
|
||||
/// <summary>
|
||||
/// Order a unit to hold.
|
||||
/// </summary>
|
||||
public IPowerContext Holds()
|
||||
{
|
||||
HoldOrder order = new HoldOrder(this.PowerContext.Power, this.Unit);
|
||||
this.Builder.OrderList.Add(order);
|
||||
return this.PowerContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Order a unit to move to a destination.
|
||||
/// </summary>
|
||||
public IPowerContext MovesTo(string provinceName, string? coast = null)
|
||||
{
|
||||
Location destination = this.Unit.Type == UnitType.Army
|
||||
? this.Builder.World.GetLand(provinceName)
|
||||
: this.Builder.World.GetWater(provinceName, coast);
|
||||
MoveOrder moveOrder = new MoveOrder(
|
||||
this.PowerContext.Power,
|
||||
this.Unit,
|
||||
this.Builder.Season,
|
||||
destination);
|
||||
this.Builder.OrderList.Add(moveOrder);
|
||||
return this.PowerContext;
|
||||
}
|
||||
|
||||
public IConvoyContext Convoys
|
||||
=> new ConvoyContext(this);
|
||||
|
||||
public ISupportContext Supports
|
||||
=> new SupportContext(this);
|
||||
}
|
||||
|
||||
private class ConvoyContext : IConvoyContext
|
||||
{
|
||||
public TestCaseBuilder Builder;
|
||||
public PowerContext PowerContext;
|
||||
public UnitContext UnitContext;
|
||||
|
||||
public ConvoyContext(UnitContext unitContext)
|
||||
{
|
||||
this.Builder = unitContext.Builder;
|
||||
this.PowerContext = unitContext.PowerContext;
|
||||
this.UnitContext = unitContext;
|
||||
}
|
||||
|
||||
public IConvoyDestinationContext Army(string provinceName, string? powerName = null)
|
||||
{
|
||||
Power power = powerName == null
|
||||
? this.PowerContext.Power
|
||||
: this.Builder.World.GetPower(powerName);
|
||||
Location location = this.Builder.World.GetLand(provinceName);
|
||||
Unit unit = this.Builder.GetOrBuildUnit(
|
||||
power, location, this.Builder.Season, UnitType.Army);
|
||||
return new ConvoyDestinationContext(this, unit);
|
||||
}
|
||||
|
||||
public IConvoyDestinationContext Fleet(
|
||||
string provinceName,
|
||||
string? coast = null,
|
||||
string? powerName = null)
|
||||
{
|
||||
Power power = powerName == null
|
||||
? this.PowerContext.Power
|
||||
: this.Builder.World.GetPower(powerName);
|
||||
Location location = this.Builder.World.GetWater(provinceName, coast);
|
||||
Unit unit = this.Builder.GetOrBuildUnit(
|
||||
power, location, this.Builder.Season, UnitType.Fleet);
|
||||
return new ConvoyDestinationContext(this, unit);
|
||||
}
|
||||
}
|
||||
|
||||
private class ConvoyDestinationContext : IConvoyDestinationContext
|
||||
{
|
||||
public TestCaseBuilder Builder;
|
||||
public PowerContext PowerContext;
|
||||
public UnitContext UnitContext;
|
||||
public Unit Target;
|
||||
|
||||
public ConvoyDestinationContext(ConvoyContext convoyContext, Unit target)
|
||||
{
|
||||
this.Builder = convoyContext.Builder;
|
||||
this.PowerContext = convoyContext.PowerContext;
|
||||
this.UnitContext = convoyContext.UnitContext;
|
||||
this.Target = target;
|
||||
}
|
||||
|
||||
public IPowerContext To(string provinceName)
|
||||
{
|
||||
Location location = this.Builder.World.GetLand(provinceName);
|
||||
ConvoyOrder order = new ConvoyOrder(
|
||||
this.PowerContext.Power,
|
||||
this.UnitContext.Unit,
|
||||
this.Target,
|
||||
this.Builder.Season,
|
||||
location);
|
||||
this.Builder.OrderList.Add(order);
|
||||
return this.PowerContext;
|
||||
}
|
||||
}
|
||||
|
||||
private class SupportContext : ISupportContext
|
||||
{
|
||||
public TestCaseBuilder Builder;
|
||||
public PowerContext PowerContext;
|
||||
public UnitContext UnitContext;
|
||||
|
||||
public SupportContext(UnitContext unitContext)
|
||||
{
|
||||
this.Builder = unitContext.Builder;
|
||||
this.PowerContext = unitContext.PowerContext;
|
||||
this.UnitContext = unitContext;
|
||||
}
|
||||
|
||||
public ISupportTypeContext Army(string provinceName, string? powerName = null)
|
||||
{
|
||||
Power power = powerName == null
|
||||
? this.PowerContext.Power
|
||||
: this.Builder.World.GetPower(powerName);
|
||||
Location location = this.Builder.World.GetLand(provinceName);
|
||||
Unit unit = this.Builder.GetOrBuildUnit(
|
||||
power, location, this.Builder.Season, UnitType.Army);
|
||||
return new SupportTypeContext(this, unit);
|
||||
}
|
||||
|
||||
public ISupportTypeContext Fleet(
|
||||
string provinceName,
|
||||
string? coast = null,
|
||||
string? powerName = null)
|
||||
{
|
||||
Power power = powerName == null
|
||||
? this.PowerContext.Power
|
||||
: this.Builder.World.GetPower(powerName);
|
||||
Location location = this.Builder.World.GetWater(provinceName, coast);
|
||||
Unit unit = this.Builder.GetOrBuildUnit(
|
||||
power, location, this.Builder.Season, UnitType.Fleet);
|
||||
return new SupportTypeContext(this, unit);
|
||||
}
|
||||
}
|
||||
|
||||
private class SupportTypeContext : ISupportTypeContext
|
||||
{
|
||||
public TestCaseBuilder Builder;
|
||||
public PowerContext PowerContext;
|
||||
public UnitContext UnitContext;
|
||||
public Unit Target;
|
||||
|
||||
public SupportTypeContext(SupportContext supportContext, Unit target)
|
||||
{
|
||||
this.Builder = supportContext.Builder;
|
||||
this.PowerContext = supportContext.PowerContext;
|
||||
this.UnitContext = supportContext.UnitContext;
|
||||
this.Target = target;
|
||||
}
|
||||
|
||||
public IPowerContext Hold()
|
||||
{
|
||||
SupportHoldOrder order = new SupportHoldOrder(
|
||||
this.PowerContext.Power,
|
||||
this.UnitContext.Unit,
|
||||
this.Target);
|
||||
this.Builder.OrderList.Add(order);
|
||||
return this.PowerContext;
|
||||
}
|
||||
|
||||
public IPowerContext MoveTo(string provinceName, string? coast = null)
|
||||
{
|
||||
Location destination = this.Target.Type == UnitType.Army
|
||||
? this.Builder.World.GetLand(provinceName)
|
||||
: this.Builder.World.GetWater(provinceName, coast);
|
||||
SupportMoveOrder order = new SupportMoveOrder(
|
||||
this.PowerContext.Power,
|
||||
this.UnitContext.Unit,
|
||||
this.Target,
|
||||
this.Builder.Season,
|
||||
destination);
|
||||
this.Builder.OrderList.Add(order);
|
||||
return this.PowerContext;
|
||||
}
|
||||
}
|
||||
}
|
115
MultiversalDiplomacyTests/TestCaseBuilderTest.cs
Normal file
115
MultiversalDiplomacyTests/TestCaseBuilderTest.cs
Normal file
@ -0,0 +1,115 @@
|
||||
using MultiversalDiplomacy.Model;
|
||||
using MultiversalDiplomacy.Orders;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MultiversalDiplomacyTests;
|
||||
|
||||
class TestCaseBuilderTest
|
||||
{
|
||||
[Test]
|
||||
public void BuilderCreatesUnits()
|
||||
{
|
||||
TestCaseBuilder setup = new(World.WithStandardMap().WithInitialSeason());
|
||||
|
||||
Assert.That(setup.World.Powers.Count(), Is.EqualTo(7), "Unexpected power count");
|
||||
Assert.That(setup.World.Units, Is.Empty, "Expected no units to be created yet");
|
||||
|
||||
setup
|
||||
["England"]
|
||||
.Army("London").Exists()
|
||||
.Fleet("Irish Sea").Exists()
|
||||
["Russia"]
|
||||
.Fleet("Saint Petersburg", "west coast").Exists();
|
||||
|
||||
Assert.That(setup.Orders, Is.Empty, "Expected no orders to be created yet");
|
||||
Assert.That(setup.World.Units, Is.Not.Empty, "Expected units to be created");
|
||||
|
||||
Unit armyLON = setup.World.GetUnitAt("London")
|
||||
?? throw new AssertionException("Expected a unit in London");
|
||||
Assert.That(armyLON.Power.Name, Is.EqualTo("England"), "Unit created with wrong power");
|
||||
Assert.That(armyLON.Type, Is.EqualTo(UnitType.Army), "Unit created with wrong type");
|
||||
|
||||
Unit fleetIRI = setup.World.GetUnitAt("Irish Sea")
|
||||
?? throw new AssertionException("Expected a unit in Irish Sea");
|
||||
Assert.That(fleetIRI.Power.Name, Is.EqualTo("England"), "Unit created with wrong power");
|
||||
Assert.That(fleetIRI.Type, Is.EqualTo(UnitType.Fleet), "Unit created with wrong type");
|
||||
|
||||
Unit fleetSTP = setup.World.GetUnitAt("Saint Petersburg")
|
||||
?? throw new AssertionException("Expected a unit in Saint Petersburg");
|
||||
Assert.That(fleetSTP.Power.Name, Is.EqualTo("Russia"), "Unit created with wrong power");
|
||||
Assert.That(fleetSTP.Type, Is.EqualTo(UnitType.Fleet), "Unit created with wrong type");
|
||||
Assert.That(
|
||||
fleetSTP.Location,
|
||||
Is.EqualTo(setup.World.GetWater("STP", "wc")),
|
||||
"Unit created on wrong coast");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuilderCreatesOrders()
|
||||
{
|
||||
TestCaseBuilder setup = new(World.WithStandardMap().WithInitialSeason());
|
||||
|
||||
Assert.That(setup.World.Powers.Count(), Is.EqualTo(7), "Unexpected power count");
|
||||
Assert.That(setup.World.Units, Is.Empty, "Expected no units to be created yet");
|
||||
Assert.That(setup.Orders, Is.Empty, "Expected no orders to be created yet");
|
||||
|
||||
setup
|
||||
["Germany"]
|
||||
.Army("Berlin").MovesTo("Kiel")
|
||||
.Army("Prussia").Holds()
|
||||
["England"]
|
||||
.Fleet("North Sea").Convoys.Army("London").To("Holland")
|
||||
["France"]
|
||||
.Army("Kiel").Supports.Army("London", powerName: "England").MoveTo("Holland")
|
||||
.Army("Munich").Supports.Army("Kiel").Hold();
|
||||
|
||||
Assert.That(setup.Orders, Is.Not.Empty, "Expected orders to be created");
|
||||
Assert.That(setup.World.Units, Is.Not.Empty, "Expected units to be created");
|
||||
List<UnitOrder> orders = setup.Orders.OfType<UnitOrder>().ToList();
|
||||
|
||||
Func<UnitOrder, bool> OrderForProvince(string name)
|
||||
=> order => order.Unit.Location.Province.Name == name;
|
||||
|
||||
UnitOrder orderBer = orders.Single(OrderForProvince("Berlin"));
|
||||
Assert.That(orderBer, Is.InstanceOf<MoveOrder>(), "Unexpected order type");
|
||||
Assert.That(
|
||||
(orderBer as MoveOrder)?.Location,
|
||||
Is.EqualTo(setup.World.GetLand("Kiel")),
|
||||
"Unexpected move order destination");
|
||||
|
||||
UnitOrder orderPru = orders.Single(OrderForProvince("Prussia"));
|
||||
Assert.That(orderPru, Is.InstanceOf<HoldOrder>(), "Unexpected order type");
|
||||
|
||||
UnitOrder orderNth = orders.Single(OrderForProvince("North Sea"));
|
||||
Assert.That(orderNth, Is.InstanceOf<ConvoyOrder>(), "Unexpected order type");
|
||||
Assert.That(
|
||||
(orderNth as ConvoyOrder)?.Target,
|
||||
Is.EqualTo(setup.World.GetUnitAt("London")),
|
||||
"Unexpected convoy order target");
|
||||
Assert.That(
|
||||
(orderNth as ConvoyOrder)?.Location,
|
||||
Is.EqualTo(setup.World.GetLand("Holland")),
|
||||
"Unexpected convoy order destination");
|
||||
|
||||
UnitOrder orderKie = orders.Single(OrderForProvince("Kiel"));
|
||||
Assert.That(orderKie, Is.InstanceOf<SupportMoveOrder>(), "Unexpected order type");
|
||||
Assert.That(
|
||||
(orderKie as SupportMoveOrder)?.Target,
|
||||
Is.EqualTo(setup.World.GetUnitAt("London")),
|
||||
"Unexpected convoy order target");
|
||||
Assert.That(
|
||||
(orderKie as SupportMoveOrder)?.Location,
|
||||
Is.EqualTo(setup.World.GetLand("Holland")),
|
||||
"Unexpected convoy order destination");
|
||||
|
||||
UnitOrder orderMun = orders.Single(OrderForProvince("Munich"));
|
||||
Assert.That(orderMun, Is.InstanceOf<SupportHoldOrder>(), "Unexpected order type");
|
||||
Assert.That(
|
||||
(orderMun as SupportHoldOrder)?.Target,
|
||||
Is.EqualTo(setup.World.GetUnitAt("Kiel")),
|
||||
"Unexpected convoy order target");
|
||||
|
||||
Assert.That(orders.Where(OrderForProvince("London")), Is.Empty, "Unexpected order");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user