2017-01-05 5 views
0

Я хочу, чтобы модуль тестировал метод анализа погоды. Мой первый подход заключался в том, чтобы позволить autofixture создать метеорологический объект и создать запрос от него. Но класс погода содержит несколько ограничений:Создание объектов с условными обозначениями

  • Влажность является процентное значение и должно быть между 1-100
  • Температура должна быть выше минимума в зависимости от единицы измерения температуры

Можно ли решить эти проблемы и стоит ли использовать этот подход или просто запрограммировать ответ на запрос и ожидаемый погодный объект?

+0

Если ваш юнит-тест является * тестированием * потребителем метеорологического объекта, я бы просто запрограммировал его. Но, как и в случае с множеством вещей, это зависит от ... – mxmissile

+0

Вот ответ на аналогичный вопрос: http://stackoverflow.com/a/22333452/126014 –

+0

@mxmissile Да, анализатор является потребителем погодного объекта. – R3turnz

ответ

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 и 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, вы можете повторно использовать это единственное соглашение на протяжении сотен тестов, поскольку оно гарантирует, что все Доменные объекты действительны.

Это не только делает ваш тестовый код более ремонтопригодным, но и делает ваш производственный код более ремонтопригодным.

+0

Это отличный ответ, но, похоже, справедливая работа над созданием действительных примитивов домена (при условии, что вы даже имеете или можете реорганизовать такие типы поддерживания инвариантов). Декларативные подходы, которые AutoFixture может понять, были бы идеальными, но аннотирование с атрибутами также кажется неправильным. Есть ли 3-й подход, о котором я спрашиваю, или мы встречаем пределы выразительности C#? – Schneider

+1

@Schneider Если есть третий подход, я не знаю об этом. Я искал много лет и, наконец, сдался на C# в пользу F #. В F # вы * делаете *, чтобы определить такие типы декларативным и лаконичным образом. –

Смежные вопросы