单元测试 - 有时有效,有时无效
Unit Test - sometimes works, sometimes not
我在 运行 下面针对后面的代码进行了单元测试。这个测试有时通过,有时失败。不知道为什么并且犹豫是否要从根本上改变事情,因为它是一个公式,有时会通过......我认为它可能与 double 类型的精度有关?不确定。想法?
[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));
}
}
不一致与精度本身无关,它更多地与浮点表示的工作方式有关。例如,如果您这样写:
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/). I also recommend reading (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
不能保证用同一个浮点数除以然后乘以得到原始浮点数。
因此,断言这一点通常不是一个好主意。最常见的测试 "almost equality" 的方法是测试相等性 "within limits"。在您的情况下,您所要做的就是在测试中定义一个您认为 "acceptable" 大于或等于 double.Epsilon 的足够小的 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));
如果相反,您必须保证准确性,一种选择是切换到非浮点类型,如十进制,但预计会影响性能。
我在 运行 下面针对后面的代码进行了单元测试。这个测试有时通过,有时失败。不知道为什么并且犹豫是否要从根本上改变事情,因为它是一个公式,有时会通过......我认为它可能与 double 类型的精度有关?不确定。想法?
[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));
}
}
不一致与精度本身无关,它更多地与浮点表示的工作方式有关。例如,如果您这样写:
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/). I also recommend reading (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
不能保证用同一个浮点数除以然后乘以得到原始浮点数。
因此,断言这一点通常不是一个好主意。最常见的测试 "almost equality" 的方法是测试相等性 "within limits"。在您的情况下,您所要做的就是在测试中定义一个您认为 "acceptable" 大于或等于 double.Epsilon 的足够小的 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));
如果相反,您必须保证准确性,一种选择是切换到非浮点类型,如十进制,但预计会影响性能。