2017-01-05 1 views
0

날씨 파싱 메서드를 단위 테스트하고 싶습니다. 내 첫 번째 접근 방식은 autofixture가 weather 객체를 만들고 그 객체로부터 쿼리 응답을 만드는 것입니다. 그러나 날씨 클래스는 여러 제한 사항이 포함 :규칙을 사용하여 객체 만들기

  • 습도는 백분율 값이며, 온도는 온도 단위

에 따라 최소 이상이어야

  • ~ 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 객체가있는 경우가 있기 때문에, 유효 걸 보장 그들은 불변성을 보호합니다. 이는 프로덕션 코드에서 유용하지만 테스트 이점도 제공합니다. 만약 당신이 좋아하면

    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을 대체 할 수 있지만,이 예를 들어 중요하지 :

    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가 이해할 수있는 선언적 접근법은 이상적이지만 속성을 사용하여 주석을 추가하는 것은 잘못되었습니다. 세 번째 접근 방식이 궁금하거나 C#의 표현의 한계를 충족 시키는가? – Schneider

    +1

    @Schneider 세 번째 접근법이있는 경우이를 인식하지 못합니다. 나는 여러 해 동안 수색을하고 마침내 F #에 찬성하여 C#을 포기했다. F #에서는 선언적이며 간결한 방식으로 이러한 유형을 정의 할 수 있습니다. –

    관련 문제