2017-01-05 4 views
0

天気予報の解析方法をユニットテストしたいと思います。私の最初のアプローチは、autofixtureにweatherオブジェクトを作成させ、そこからクエリ応答を作成することでした。しかし、天候クラスは、複数の制限が含まれています慣例によるオブジェクトの作成

  • 湿度がパーセント値であると温度が温度単位

に応じて、最小値以上でなければなりません

  • 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(); 
        } 
    } 
    

    これは、あなたがHumidityCelciusオブジェクトを持っている場合、彼らはので、有効だということを保証彼らは不変量を保護します。これは、プロダクションコードでは貴重ですが、テストのメリットもあります。

    Weatherは、単純に、今、次のようになります。

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

    あなたが好きな場合は、同様WeatherためEqualsGetHashCodeを上書きすることができますが、それはこの例では重要ではありません。

    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); 
    } 
    

    このテストに合格します。

    これは1回のテストで多くの作業のように見えますが、すべてのドメイン固有のカスタマイズをMyConventionsで収集すると、その1つのコンベンションを何百ものテストにわたって再利用できます。ドメインオブジェクトは有効です。

    テストコードのメンテナンス性が向上するだけでなく、生産コードの保守性も向上します。

    +0

    これはすばらしい答えですが、有効なドメインプリミティブを作成するにはかなりの労力がかかるようです(あなたが持っていると仮定したり、そのような不変のメンテナンスタイプにリファクタリングすることができます)。 AutoFixtureが理解できる宣言的なアプローチは理想的ですが、属性を注釈付けすることも間違っています。 3番目のアプローチがあるのだろうか、それともC#の表現力の限界を満たすのだろうか? – Schneider

    +1

    @シュナイダー第3のアプローチがある場合、私はそれを認識していません。私は何年もの間検索し、ついにF#を支持してC#をあきらめました。 F#では、そのような型を宣言的かつ簡潔な方法で定義します。 –