我应该如何对一个执行数组数值插值的类进行单元测试?

本文关键字:插值 单元测试 数组 执行数 执行 一个 我应该 | 更新日期: 2023-09-27 18:18:54

我有一个项目,其中一些类执行数值插值,也就是说,给定一组已知位置的点,我可以要求网格节点之间的点的位置,可以这么说。

由于这些方法根据定义不返回精确值,我不知道该如何对它们进行单元测试。

例如,下面的代码测试如果我给插值器一个类似的零数组,它返回一个零数组,并且它工作,但我怀疑它工作,因为我实际上并没有在这里重新采样,只是再次询问相同的位置。

[TestMethod]
public void Interpolate_ZeroIn_ZeroOut()
{
    var Xvalues = new double[] {1,2,3,4,5,6,7,8,9,10};
    var Yvalues = new double[] {0,0,0,0,0,0,0,0,0,0};
    List<Point> points = 
        Enumerable.Zip(Xvalues, Yvalues, (x,y) => new Point(x,y)).ToList();
    int interpolation_order = 5;
    FourierIterpolator target = new FourierInterpolator(points, interpolation_order);
    var output = Xvalues.Select(p => target.Interpolate(p)).ToList();
    CollectionAssert.AreEqual(output, Yvalues);
}

问题是:在实际重采样时,新的点将在输入点之间,所以我不能使用CollectionAssert.AreEqual。此外,使用一些平滑的插值方法,数组不会相等,只能近似。

那么,我的问题是:

当测试涉及近似/插值的数值方法时,建议使用什么断言?

我应该如何对一个执行数组数值插值的类进行单元测试?

你的插值技术是确定性的吗?(Deterministic意味着:如果你的方法用相同的参数调用两次,它会在两次情况下返回完全相同的结果吗?)

如果是的,

  • 通过你的函数运行几个值,
  • 使用外部方法来验证结果是否正确(笔和纸,或一些数学工具),
  • 添加这些值作为测试用例。
这将确保测试用例检测到任何对方法的破坏性更改。一个缺点是,如果您改进插值技术,则您的案例将失败,从而产生不同的结果。这并不一定是件坏事,因为它提醒您,您的方法现在返回不同的结果。

如果你的插值技术不是确定性的,也就是说,如果它使用了一些随机性来源,你可能能够断言这些值在一些合理的误差范围内。

目前很难看到测试的输入数据是什么。我建议您使测试数据显式且易于查看。我也不认为你需要10或100点来验证你的方法是否有效。大概2-3分就够了:

var points = new []{ new Point(0,0), new Point(1,0), new Point(2,0) });

接下来你应该提供预期的值:

double[] expected = { 0.33, 0.66, 1.66 };

最后-检查实际值是否等于或接近期望值:

[TestMethod]
public void Interpolar_ZeroIn_ZeroOut()
{
    var points = new []{ new Point(0,0), new Point(1,0), new Point(2,0) });
    // calculate expected values manually
    double[] expected = { 0.33, 0.66, 1.66 };    
    int ordem = 5;
    var interpolator = new InterpoladorFourier(points, ordem);
    for(int i = 0; i < points.Length; i++)        
       Assert.AreEqual(expected[i], interpolator.Interpolar(points[i].X));
}

如果期望的数据不准确,那么您可以为断言提供delta:

Assert.AreEqual(expected[i], interpolator.Interpolar(points[i].X), 0.01);

或者更好——你可以创建一个方法,以非常可读的方式创建点:

var points = CreatePoints("0,0", "1,0", "2,0"); 

使用这个辅助方法:

private Point[] CreatePoints(params string[] points)
{
    List<Point> result = new List<Point>();
    foreach(var s in points)
    {
        var parts = s.Split(',');
        var x = Double.Parse(parts[0]);
        var y = Double.Parse(parts[1]);            
        var point = new Point(x,y);
        result.Add(point);
    }
    return result.ToArray();
}

我通过谷歌来到这里,因为我自己正在寻找一些答案,但我目前的方法可能比这里提供的更好,所以我将分享给将来参考。

大多数(如果不是全部)插值方法都有相当数量的函数,它们可以完美地插值或至少达到机器精度。比如一个三次样条曲线它的多项式在3次以上。我只是测试了合理数量的多项式,这些多项式有不同的系数,不同的间隔,不同的点数……计算均方根误差并"断言";如果它接近机器精度(输入rms_err <10 * eps)。选择合适的多项式/函数/参数需要对具体的插值方法和浮点误差有一定的了解,但这是可行的。

另一种方法是与具有相同或类似实现的可用库进行比较,要么直接导出值,要么直接使用库。例如,我测试了我的2D插值实现与使用相同方法的可用1D实现,当然是一次沿一个维度。