Fluent Assertions:大致比较两个 2D 矩形阵列

Posted

技术标签:

【中文标题】Fluent Assertions:大致比较两个 2D 矩形阵列【英文标题】:Fluent Assertions: Approximately compare two 2D rectangular arrays 【发布时间】:2016-07-31 20:42:23 【问题描述】:

我可以在 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);
    

但是有没有更简洁的方法来实现这一点(没有 for 循环)?例如,与此相同的内容(用于一维数组):

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);

上述一维数组的解决方案来自问题:Fluent Assertions: Compare two numeric collections approximately

【问题讨论】:

如果您能够使用float[][],而不是float[,],那么您可以扩展链接的答案以具有类似:expectedArray.Should().Equal(calculatedArray, (rowLeft, rowRight) =&gt; rowLeft.Should().Equal(rowRight, (left, right) =&gt; Math.Abs(left - right) &lt;= 0.01); return true; ); @forsvarir float[,] 用于保存矩阵的元素,因此在这种情况下,我的首选是保留矩形数组而不是使用锯齿状数组。虽然很高兴知道可以以这种方式使用重载。 【参考方案1】:

目前框架中似乎没有任何东西支持这一点。如果您不想在测试中使用循环,那么一种选择是添加您自己的扩展来涵盖这种情况。

这有两个要素。首先是添加一个扩展方法,将Should 能力添加到二维数组:

public static class FluentExtensionMethods

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

然后您需要实现实际的断言类,其中将包含比较循环:

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;
    

然后您可以像其他断言一样在测试中使用它:

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

我认为您的评论是在询问您如何测试我建议的扩展代码。答案是,就像您测试其他任何东西一样,传入值并验证输出。我在下面添加了一些测试(使用 Nunit)来涵盖一些关键场景。需要注意的是,场景的数据是不完整的(它似乎超出了范围,并不难生成)。测试使用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...
;

【讨论】:

这是一个很好的解决方案。现在要递归了,有没有办法编写单元测试来确保这段代码正确运行?或者这是必须手动检查代码的情况(在调用它的单元测试中点击 Assert 之前,fluentassertions 失败案例将退出 Generic2DArrayAssertions)。 @Ayb4btu 我想你在问如何验证扩展是否有效,我添加了一些测试来验证行为,如果这不是你想要的,那么我就是不确定我是否理解您的问题。 是的,这就是我要问的。我不知道如何捕获和处理失败案例消息,因此拥有 .Message.Should().Be 扩展名非常简洁。感谢您的出色回答。【参考方案2】:

我尚未对此进行全面测试,但以下似乎可行。

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>());

【讨论】:

欢迎来到 Stack Overflow!请注意,您正在回答一个非常古老且已经回答的问题。这是How to Answer 的指南。

以上是关于Fluent Assertions:大致比较两个 2D 矩形阵列的主要内容,如果未能解决你的问题,请参考以下文章

Fluent Assertions - null 和空字符串比较 [关闭]

ShouldBeEquivalentTo 的 C# Fluent Assertions 全局选项

Fluent Assertions:两个数组列表的等价性,数组需要严格排序

Fluent Assertions 能否对 IEnumerable<string> 使用不区分字符串的比较?

如果使用 Fluent Assertions 的顺序不同,如何断言两个列表不等价

Fluent Assertions Should().BeEquivalentTo 只有私有字段