简体   繁体   English

Fluent Assertions:近似比较两个二维矩形数组

[英]Fluent Assertions: Approximately compare two 2D rectangular arrays

I'm able to approximately compare two 2D rectangular arrays in Fluent Assertions like this:我能够大致比较 Fluent Assertions 中的两个二维矩形数组,如下所示:

float precision = 1e-5f;
float[,] expectedArray = new float[,] { { 3.1f, 4.5f}, { 2, 4} };
float[,] calculatedArray = new float[,] { { 3.09f, 4.49f}, { 2, 4} };

for (int y = 0; y < 2; ++y)
{
    for (int x = 0; x < 2; ++x)
    {
        calculatedArray[y,x].Should().BeApproximately(expectedArray[y,x], precision);
    }
}

but is there a cleaner way of achieving this (without the for loops)?但是有没有更干净的方法来实现这一点(没有 for 循环)? For example, something in the same vein as this (which is for 1D arrays):例如,与此相同的内容(用于一维数组):

double[] source = { 10.01, 8.01, 6.01 };
double[] target = { 10.0, 8.0, 6.0  };

source.Should().Equal(target, (left, right) => Math.Abs(left-right) <= 0.01);

The above solution for a 1D array comes from the question: Fluent Assertions: Compare two numeric collections approximately一维数组的上述解决方案来自以下问题: Fluent Assertions: Compare two numeric collection大约

There doesn't appear to be anything currently in the framework that supports this.目前框架中似乎没有任何东西支持这一点。 If you don't want to have the loop in your tests, then one option would be for you to add your own extensions to cover this scenario.如果您不想在测试中使用循环,那么您可以选择添加自己的扩展来覆盖这种情况。

There are two elements to this.这有两个要素。 The first is to add an extension method that adds the Should ability to 2D arrays:第一个是添加一个扩展方法,为 2D 数组添加Should能力:

public static class FluentExtensionMethods
{
    public static Generic2DArrayAssertions<T> Should<T>(this T[,] actualValue)
    {
        return new Generic2DArrayAssertions<T>(actualValue);
    }
}

You then need to implement the actual assertion class, which will contain the comparison loop:然后,您需要实现实际的断言类,该类将包含比较循环:

public class Generic2DArrayAssertions<T> 
{
    T[,] _actual;

    public Generic2DArrayAssertions(T[,] actual)
    {
        _actual = actual;
    }

    public bool Equal(T[,] expected, Func<T,T, bool> func)
    {
        for (int i = 0; i < expected.Rank; i++)
            _actual.GetUpperBound(i).Should().Be(expected.GetUpperBound(i), 
                                                 "dimensions should match");

        for (int x = expected.GetLowerBound(0); x <= expected.GetUpperBound(0); x++)
        {
            for (int y = expected.GetLowerBound(1); y <= expected.GetUpperBound(1); y++)
            {
                func(_actual[x, y], expected[x, y])
                     .Should()
                     .BeTrue("'{2}' should equal '{3}' at element [{0},{1}]",
                      x, y, _actual[x,y], expected[x,y]);
            }
        }

        return true;
    }
}

You can then use it in your tests like other assertions:然后,您可以像其他断言一样在测试中使用它:

calculatedArray.Should().Equal(expectedArray, 
                               (left,right)=> Math.Abs(left - right) <= 0.01);

I think your comment is asking how you go about testing the extension code I'm suggesting.我认为您的评论是在询问您如何测试我建议的扩展代码。 The answer is, the same way you go about testing anything else, pass in values and validate the output.答案是,与测试其他任何东西的方式相同,传递值并验证输出。 I've added some tests below (using Nunit) to cover some of the key scenarios.我在下面添加了一些测试(使用 Nunit)来涵盖一些关键场景。 Some things to note, the data for the scenarios is incomplete (it seems out of scope and isn't that hard to generate).需要注意的是,场景的数据是不完整的(它似乎超出了范围并且不难生成)。 The tests are using a func of left == right , since the point is to test the extension, not the evaluation of the approximation.测试使用的是left == right的函数,因为重点是测试扩展,而不是近似值的评估。

[TestCaseSource("differentSizedScenarios")]
public void ShouldThrowIfDifferentSizes(float[,] actual, float[,] expected)
{
    Assert.Throws<AssertionException>(()=>actual.Should().Equal(expected, (l, r) => l == r)).Message.Should().Be(string.Format("Expected value to be {0} because dimensions should match, but found {1}.", expected.GetUpperBound(0), actual.GetUpperBound(0)));
}

[TestCaseSource("missMatchedScenarios")]
public void ShouldThrowIfMismatched(int[,] actual, int[,] expected, int actualVal, int expectedVal, string index)
{
    Assert.Throws<AssertionException>(()=>actual.Should().Equal(expected, (l, r) => l.Equals(r))).Message.Should().Be(string.Format("Expected True because '{0}' should equal '{1}' at element [{2}], but found False.", actualVal, expectedVal, index));
}

[Test]
public void ShouldPassOnMatched()
{
    var expected = new float[,] { { 3.1f, 4.5f }, { 2, 4 } };
    var actual = new float[,] { { 3.1f, 4.5f }, { 2, 4 } };
    actual.Should().Equal(expected, (l, r) => l.Equals(r));
}

static object[] differentSizedScenarios = 
{
    new object[] {
        new float[,] { { 3.1f, 4.5f }, { 2, 4 } },
        new float[,] { { 3.1f, 4.5f }, { 2, 4 }, {1,2} }
    },
    new object[] {
        new float[,] { { 3.1f, 4.5f }, { 2, 4 } },
        new float[,] { { 3.1f, 4.5f }}
    }
    // etc...
};
static object[] missMatchedScenarios = 
{
    new object[] {
        new int[,] { { 1, 2}, { 3, 4 } },
        new int[,] { { 11, 2}, { 3, 4 } }
        ,1, 11, "0,0"
    },
    new object[] {
        new int[,] { { 1, 2}, { 3, 14 } },
        new int[,] { { 1, 2}, { 3, 4 } }
        ,14, 4, "1,1"
    },
    // etc...
};

I haven't fully tested this, but the following seems to work.我还没有完全测试过这个,但以下似乎有效。

float precision = 0.1f; // Test passes with this level of precision.
//float precision = 0.01f; // Test fails with this level of precision.
float[,] expectedArray = new float[,] { { 3.1f, 4.5f }, { 2, 4 } };
float[,] calculatedArray = new float[,] { { 3.09f, 4.49f }, { 2, 4 } };

calculatedArray.Should().BeEquivalentTo(
    expectedArray,
    options => options
        .ComparingByValue<float>()
        .Using<float>(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, precision))
        .WhenTypeIs<float>());

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM