首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用约定创建对象

用约定创建对象
EN

Stack Overflow用户
提问于 2017-01-05 15:10:35
回答 1查看 87关注 0票数 0

我想对天气分析方法进行单元测试。我的第一种方法是让自动夹具创建一个天气对象,而不是从它创建查询响应。但是天气类包含多个限制:

  • 湿度是百分比,必须在1-100之间。
  • 温度必须高于最低温度,取决于温度单位。

是否有可能解决这些问题,是否值得使用这种方法,或者只使用硬代码、查询响应和预期的天气对象?

EN

回答 1

Stack Overflow用户

发布于 2017-10-13 20:48:13

作为其他地方概述,我推荐一种解决方案,让测试驱动的开发为您的设计提供反馈。重构为一个良好的域模型没有把湿度和温度当作原语来处理。例如,为这两个对象创建一个新的值对象:

代码语言:javascript
复制
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类型看起来类似:

代码语言:javascript
复制
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();
    }
}

这保证了如果您有一个HumidityCelcius对象,它们是有效的,因为它们保护它们的不变量。这在您的生产代码中很有价值,但也提供了测试方面的好处。

Weather看起来就像这样,现在:

代码语言:javascript
复制
public class Weather
{
    public Humidity Humidity { get; }
    public Celcius Temperature { get; }

    public Weather(Humidity humidity, Celcius temperature)
    {
        this.Humidity = humidity;
        this.Temperature = temperature;
    }
}

如果您愿意,也可以为EqualsGetHashCode覆盖Weather,但对于本例来说并不重要。

对于AutoFixture,现在可以为这两种类型定义自定义:

代码语言:javascript
复制
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);
        }
    }
}

代码语言:javascript
复制
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中收集这些(以及其他)

代码语言:javascript
复制
public class MyConventions : CompositeCustomization
{
    public MyConventions() : base(
        new CelciusCustomization(),
        new HumidityCustomization())
    {
    }
}

现在,您可以编写这样简单的测试:

代码语言:javascript
复制
[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中收集所有特定于域的自定义,您可以在数百个测试中重用该单一约定,因为它可以保证所有域对象都是有效的。

它不仅使您的测试代码更易于维护,还使您的产品代码更易于维护。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41488347

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档