Fluent Assertions:近似比较两个二维矩形数组
Fluent Assertions: Approximately compare two 2D rectangular arrays
我可以像这样在 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
框架中目前似乎没有任何支持此功能的内容。如果您不想在测试中使用循环,那么您可以选择添加自己的扩展来涵盖这种情况。
这有两个要素。第一个是添加一个扩展方法,为二维数组添加 Should
能力:
public static class FluentExtensionMethods
{
public static Generic2DArrayAssertions<T> Should<T>(this T[,] actualValue)
{
return new Generic2DArrayAssertions<T>(actualValue);
}
}
然后您需要实现实际断言 class,其中将包含比较循环:
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...
};
我还没有完全测试过这个,但下面的方法似乎有效。
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>());
我可以像这样在 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
框架中目前似乎没有任何支持此功能的内容。如果您不想在测试中使用循环,那么您可以选择添加自己的扩展来涵盖这种情况。
这有两个要素。第一个是添加一个扩展方法,为二维数组添加 Should
能力:
public static class FluentExtensionMethods
{
public static Generic2DArrayAssertions<T> Should<T>(this T[,] actualValue)
{
return new Generic2DArrayAssertions<T>(actualValue);
}
}
然后您需要实现实际断言 class,其中将包含比较循环:
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...
};
我还没有完全测试过这个,但下面的方法似乎有效。
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>());