我想对天气分析方法进行单元测试。我的第一种方法是让自动夹具创建一个天气对象,而不是从它创建查询响应。但是天气类包含多个限制:
是否有可能解决这些问题,是否值得使用这种方法,或者只使用硬代码、查询响应和预期的天气对象?
发布于 2017-10-13 20:48:13
作为其他地方概述,我推荐一种解决方案,让测试驱动的开发为您的设计提供反馈。重构为一个良好的域模型没有把湿度和温度当作原语来处理。例如,为这两个对象创建一个新的值对象:
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中收集所有特定于域的自定义,您可以在数百个测试中重用该单一约定,因为它可以保证所有域对象都是有效的。
它不仅使您的测试代码更易于维护,还使您的产品代码更易于维护。
https://stackoverflow.com/questions/41488347
复制相似问题