我想单独测试一个天气解析方法。我的第一种方法是让autofixture创建一个天气对象,然后从中创建查询响应。但天气等级包含多个限制:
是否有可能解决这个问题,是否值得使用这种方法或只是硬编码查询响应和预期的天气对象?
答案 0 :(得分:1)
作为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
的{{1}}和GetHashCode
,但这对此示例并不重要。
对于AutoFixture,您现在可以为这两种类型定义自定义:
Weather
和
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);
}
中收集所有特定于域的自定义项,则可以在数百个测试中重用该单个约定,因为它保证所有域对象都有效。
它不仅可以使您的测试代码更易于维护,还可以使您的生产代码更易于维护。