Как outlined elsewhere, я бы порекомендовал решение, в котором вы можете дать тестовую разработку, обеспечивающую обратную связь по вашему дизайну. Вместо обработки влажности и температуры в качестве примитивов, refactor to a good domain model. В качестве примера, создать новый объект значения для обоих:
public struct Humidity
{
public readonly byte percentage;
public Humidity(byte percentage)
{
if (100 < percentage)
throw new ArgumentOutOfRangeException(
nameof(percentage),
"Percentage must be a number between 0 and 100.");
this.percentage = percentage;
}
public static explicit operator byte(Humidity h)
{
return h.percentage;
}
public static explicit operator int(Humidity h)
{
return h.percentage;
}
public override bool Equals(object obj)
{
if (obj is Humidity)
return ((Humidity)obj).percentage == this.percentage;
return base.Equals(obj);
}
public override int GetHashCode()
{
return this.percentage.GetHashCode();
}
}
Тип Celcius
похож:
public struct Celcius
{
private readonly decimal degrees;
public Celcius(decimal degrees)
{
if (degrees < -273.15m)
throw new ArgumentOutOfRangeException(
nameof(degrees),
"Degrees Celsius must be equal to, or above, absolute zero.");
this.degrees = degrees;
}
public static explicit operator decimal(Celcius c)
{
return c.degrees;
}
public override bool Equals(object obj)
{
if (obj is Celcius)
return ((Celcius)obj).degrees == this.degrees;
return base.Equals(obj);
}
public override int GetHashCode()
{
return this.degrees.GetHashCode();
}
}
Это гарантирует, что если у вас есть Humidity
или Celcius
объект, они действительны, потому что они защищают свои инварианты. Это полезно в вашем производственном коде, но также дает преимущества тестирования.
Weather
просто выглядит так, теперь:
public class Weather
{
public Humidity Humidity { get; }
public Celcius Temperature { get; }
public Weather(Humidity humidity, Celcius temperature)
{
this.Humidity = humidity;
this.Temperature = temperature;
}
}
Вы можете переопределить Equals
и GetHashCode
для Weather
, а также, если вы хотите, но это не важно для этого примера.
Для AutoFixture, теперь вы можете определить настройки для обоих типов:
public class HumidityCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new HumidityBuilder());
}
private class HumidityBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var t = request as Type;
if (t == null || t != typeof(Humidity))
return new NoSpecimen();
var d =
context.Resolve(
new RangedNumberRequest(
typeof(byte),
byte.MinValue,
(byte)100));
return new Humidity((byte)d);
}
}
}
и
public class CelciusCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new CelciusBuilder());
}
private class CelciusBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var t = request as Type;
if (t == null || t != typeof(Celcius))
return new NoSpecimen();
var d =
context.Resolve(
new RangedNumberRequest(
typeof(decimal),
-273.15m,
decimal.MaxValue));
return new Celcius((decimal)d);
}
}
}
Вы можете получить те (и другие) в CompositeCustomization
:
public class MyConventions : CompositeCustomization
{
public MyConventions() : base(
new CelciusCustomization(),
new HumidityCustomization())
{
}
}
Теперь вы можете написать тесты так же просто, как это:
[Fact]
public void FixtureCanCreateValidWeather()
{
var fixture = new Fixture().Customize(new MyConventions());
var actual = fixture.Create<Weather>();
Assert.True((int)actual.Humidity <= 100);
Assert.True(-273.15m <= (decimal)actual.Temperature);
}
Этот тест проходит.
Конечно, это похоже на большую работу для одного теста, но дело в том, что если вы соберете все настройки для домена в MyConventions
, вы можете повторно использовать это единственное соглашение на протяжении сотен тестов, поскольку оно гарантирует, что все Доменные объекты действительны.
Это не только делает ваш тестовый код более ремонтопригодным, но и делает ваш производственный код более ремонтопригодным.
Если ваш юнит-тест является * тестированием * потребителем метеорологического объекта, я бы просто запрограммировал его. Но, как и в случае с множеством вещей, это зависит от ... – mxmissile
Вот ответ на аналогичный вопрос: http://stackoverflow.com/a/22333452/126014 –
@mxmissile Да, анализатор является потребителем погодного объекта. – R3turnz