Модульный тест - иногда работает, иногда нет - PullRequest
0 голосов
/ 17 сентября 2018

У меня ниже приведен модульный тест, работающий с кодом, который следует.Этот тест иногда проходит, иногда не проходит.Не уверен, почему и стесняюсь радикально изменить положение вещей, так как хорошо, это формула, а иногда и проходит ... Я думаю, что это может иметь какое-то отношение к точности типа double?Точно сказать не могу.Мысли?

Good test

Bad test

[TestMethod]
public void CircleFromCircumference()
{
    var random = new Random();
    var circumference = random.NextDouble();
    var circle = new Circle("My circle", circumference, Circle.CircleDimensions.Circumference);

    var var1 = circumference - circle.Circumference;
    var var2 = circumference - 2 * Math.PI * circle.Radius;
    var var3 = circumference - Math.PI * circle.Diameter;
    var var4 = Math.Pow(circumference / (2 * Math.PI), 2) * Math.PI - circle.Area;

    Assert.IsTrue(
        circumference - circle.Circumference <= 0 //circumference
        && circumference - 2 * Math.PI * circle.Radius <= 0 //radius
        && circumference - Math.PI * circle.Diameter <= 0 //diameter
        && Math.Pow(circumference / (2 * Math.PI), 2) * Math.PI - circle.Area <= 0 //area
        && string.IsNullOrEmpty(circle.ShapeException));
}



using System;
using System.Runtime.Serialization;

namespace Shapes
{
    [DataContract]
    public class Circle : Shape
    {
        [DataMember] public double Radius { get; set; }
        [DataMember] public double Diameter { get; set; }
        [DataMember] public double Circumference { get; set; }

        /// <summary>
        /// The name of the value you are sending. Radius is the default
        /// </summary>
        public enum CircleDimensions
        {
            Circumference = 1,
            Area = 2,
            Diameter = 3
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="circleName">The name of your circle</param>
        /// <param name="dimension">The value of the dimension you are providing</param>
        /// <param name="circleDimensions">The name of the value you are providing. Radius is default</param>
        public Circle(string circleName, double dimension = 0, CircleDimensions circleDimensions = 0)
        {
            this.ShapeName = circleName;
            if (dimension <= 0)
            {
                this.ShapeException = "Parameters must be greater than zero";
                return;
            }

            switch (circleDimensions)
            {
                case CircleDimensions.Circumference:
                    //radius from Circumference
                    this.Circumference = dimension;
                    this.Radius = this.RadiusFromCircumference(dimension);
                    this.Area = this.CalculateArea(this.Radius);
                    this.Diameter = this.CalculateDiameter(this.Radius);
                    break;
                case CircleDimensions.Area:
                    //radius from Area
                    break;
                case CircleDimensions.Diameter:
                    //radius from diameter
                    break;
                default: //calculate from radius
                    this.Radius = dimension;
                    this.Diameter = this.CalculateDiameter(dimension);
                    this.Circumference = this.CalculateCircumference(dimension);
                    this.Area = this.CalculateArea(dimension);
                    break;
            }
        }

        private double RadiusFromCircumference(double dimension) => dimension / (2 * Math.PI);

        private double CalculateCircumference(double dimension) => 2 * Math.PI * dimension;
        private double CalculateDiameter(double dimension) => 2 * dimension;
        private double CalculateArea(double dimension) =>
            Math.PI * (Math.Pow(dimension, 2));
    }
}

1 Ответ

0 голосов
/ 17 сентября 2018

Несоответствие не имеет ничего общего с точностью как таковой, оно больше связано с тем, как работает представление с плавающей запятой.Например, если вы напишите это:

for (float f = 0.0f; f != 1.0f; f+=0.1f)
{
   Console.WriteLine(f);
}

Он никогда не завершится.Потому что 0.1 не имеет точного представления в двоичной форме.(https://www.exploringbinary.com/why-0-point-1-does-not-exist-in-floating-point/). Я также рекомендую прочитать (https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)

). Возвращаясь к проблеме, в вашем коде вы получаете радиус, используя это:

dimension / (2 * Math.PI); //passed in dimension is the Circumference, returns radius

Изатем в своем тесте вы утверждаете, что:

circumference - 2 * Math.PI * circle.Radius <= 0

Деление, а затем умножение на одно и то же число с плавающей запятой не гарантируют, что в результате вы получите исходное число с плавающей запятой.

Таким образомв целом это плохая идея утверждать это. Наиболее распространенный способ проверки "почти равенства" - это проверка равенства "в определенных пределах". В вашем случае все, что вам нужно сделать, это определить достаточно маленький эпсилон, который вы считаете "«приемлемо», больше или равно double.Epsilon в ваших тестах.

var epsilon = double.Epsilon;
Assert.IsTrue(
    Math.Abs(circumference - circle.Circumference) <= epsilon //circumference
    && Math.Abs(circumference - 2 * Math.PI * circle.Radius) <= epsilon //radius
    && Math.Abs(circumference - Math.PI * circle.Diameter) <= epsilon //diameter
    && Math.Abs(Math.Pow(circumference / (2 * Math.PI), 2) * Math.PI - circle.Area) <= epsilon //area
    && string.IsNullOrEmpty(circle.ShapeException));

Если вместо этого вам нужно гарантировать точность, одним из вариантов является переключение на тип с плавающей запятой, например десятичный, но ожидайте производительностьудар.

...