我如何使用 Shouldly 进行自定义断言并维护特定于调用站点的断言消息?
How can I have custom asserts with Shouldly and maintain the call-site-specific assertion messages?
我在我的 xUnit 测试中使用 the excellent Shouldly
library,我发现自己在不同的测试中使用了断言的集合序列,所以我将它们组合到新的断言扩展方法中 - 但是当我这样做时我丢失了 Shouldly
的上下文断言消息。
这是我的旧代码,它与 Shouldly
一起工作以在 Shouldly
断言错误中包含源代码级信息和调用站点上下文:
[Fact]
public void Dict_should_contain_valid_Foobar_Bar_entry()
{
IDictionary<String,Bar> dict = ...
dict.TryGetValue( "Foobar", out Bar bar ).ShouldBeTrue();
bar.ShouldNotBeNull();
bar.ChildList.Count.ShouldBe( expected: 3 );
bar.Message.ShouldBeNull();
}
[Fact]
public void Dict_should_contain_valid_Barbaz_Bar_entry()
{
IDictionary<String,Bar> dict = ...
dict.TryGetValue( "Barbaz", out Bar bar ).ShouldBeTrue();
bar.ShouldNotBeNull();
bar.ChildList.Count.ShouldBe( expected: 3 );
bar.Message.ShouldBeNull();
}
我在同一项目中将其转换为这种新的扩展方法:
public static void ShouldBeValidBar( this IDictionary<String,Bar> dict, String barName )
{
dict.ShouldNotBeNull();
dict.TryGetValue( barName, out Bar bar ).ShouldBeTrue();
bar.ShouldNotBeNull();
bar.ChildList.Count.ShouldBe( expected: 3 );
bar.Message.ShouldBeNull();
}
因此我的测试更改为:
[Fact]
public void Dict_should_contain_valid_Foobar_Bar_entry()
{
IDictionary<String,Bar> dict = ...
dict.ShouldBeValidBar( "Foobar" );
}
[Fact]
public void Dict_should_contain_valid_Barbaz_Bar_entry()
{
IDictionary<String,Bar> dict = ...
dict.ShouldBeValidBar( "Barbaz" );
}
...但现在我的 Shouldly assert 消息不包含来自 Dict_should_contain_valid_Foobar_Bar_entry
的任何上下文信息,而是仅包含来自 ShouldBeValidBar
.
的上下文
如何指示 Shouldly
忽略 ShouldBeValidBar
的上下文并改用其父调用站点?
长话短说:
将 [ShouldlyMethods]
属性添加到您的自定义断言扩展方法 类(不是单独的扩展方法):
[ShouldlyMethods] // <-- This, right here!
public static class MyShouldlyAssertions
{
public static void ShouldBeValidBar( this IDictionary<String,Bar> dict, String barName )
{
[...]
}
}
长版:
经过一些谷歌搜索后, - and after reading the source of Shouldly's secret-sauce: SourceCodeTextGetter
, I see that it determines which entries in the stack-trace can be overlooked by the presence of the [ShouldlyMethods]
attribute (Shouldly.ShouldlyMethodsAttribute
) 在堆栈跟踪的每个帧中包含方法的类型:
void ParseStackTrace(StackTrace trace)
{
[...]
while (ShouldlyFrame == null || currentFrame.GetMethod().IsShouldlyMethod())
{
if (currentFrame.GetMethod().IsShouldlyMethod())
ShouldlyFrame = currentFrame;
[...]
}
[...]
}
internal static bool IsShouldlyMethod(this MethodBase method)
{
if (method.DeclaringType == null)
return false;
return
method
.DeclaringType
.GetCustomAttributes( typeof(ShouldlyMethodsAttribute), true )
.Any()
||
(
method.DeclaringType.DeclaringType != null
&&
method
.DeclaringType
.DeclaringType
.GetCustomAttributes( typeof(ShouldlyMethodsAttribute), true )
.Any()
);
}
所以只需将 [ShouldlyMethods]
属性添加到我的扩展方法的容器中即可 类:
[ShouldlyMethods]
public static class ShouldlyAssertionExtensions
{
public static void ShouldBeValidBar( this IDictionary<String,Bar> dict, String barName )
{
dict.ShouldNotBeNull();
dict.TryGetValue( barName, out Bar bar ).ShouldBeTrue();
bar.ShouldNotBeNull();
bar.ChildList.Count.ShouldBe( expected: 3 );
bar.Message.ShouldBeNull();
}
}
现在我的断言错误具有 ShouldBeValidBar
的调用站点的上下文。万岁!
我在我的 xUnit 测试中使用 the excellent Shouldly
library,我发现自己在不同的测试中使用了断言的集合序列,所以我将它们组合到新的断言扩展方法中 - 但是当我这样做时我丢失了 Shouldly
的上下文断言消息。
这是我的旧代码,它与 Shouldly
一起工作以在 Shouldly
断言错误中包含源代码级信息和调用站点上下文:
[Fact]
public void Dict_should_contain_valid_Foobar_Bar_entry()
{
IDictionary<String,Bar> dict = ...
dict.TryGetValue( "Foobar", out Bar bar ).ShouldBeTrue();
bar.ShouldNotBeNull();
bar.ChildList.Count.ShouldBe( expected: 3 );
bar.Message.ShouldBeNull();
}
[Fact]
public void Dict_should_contain_valid_Barbaz_Bar_entry()
{
IDictionary<String,Bar> dict = ...
dict.TryGetValue( "Barbaz", out Bar bar ).ShouldBeTrue();
bar.ShouldNotBeNull();
bar.ChildList.Count.ShouldBe( expected: 3 );
bar.Message.ShouldBeNull();
}
我在同一项目中将其转换为这种新的扩展方法:
public static void ShouldBeValidBar( this IDictionary<String,Bar> dict, String barName )
{
dict.ShouldNotBeNull();
dict.TryGetValue( barName, out Bar bar ).ShouldBeTrue();
bar.ShouldNotBeNull();
bar.ChildList.Count.ShouldBe( expected: 3 );
bar.Message.ShouldBeNull();
}
因此我的测试更改为:
[Fact]
public void Dict_should_contain_valid_Foobar_Bar_entry()
{
IDictionary<String,Bar> dict = ...
dict.ShouldBeValidBar( "Foobar" );
}
[Fact]
public void Dict_should_contain_valid_Barbaz_Bar_entry()
{
IDictionary<String,Bar> dict = ...
dict.ShouldBeValidBar( "Barbaz" );
}
...但现在我的 Shouldly assert 消息不包含来自 Dict_should_contain_valid_Foobar_Bar_entry
的任何上下文信息,而是仅包含来自 ShouldBeValidBar
.
如何指示 Shouldly
忽略 ShouldBeValidBar
的上下文并改用其父调用站点?
长话短说:
将 [ShouldlyMethods]
属性添加到您的自定义断言扩展方法 类(不是单独的扩展方法):
[ShouldlyMethods] // <-- This, right here!
public static class MyShouldlyAssertions
{
public static void ShouldBeValidBar( this IDictionary<String,Bar> dict, String barName )
{
[...]
}
}
长版:
经过一些谷歌搜索后,SourceCodeTextGetter
, I see that it determines which entries in the stack-trace can be overlooked by the presence of the [ShouldlyMethods]
attribute (Shouldly.ShouldlyMethodsAttribute
) 在堆栈跟踪的每个帧中包含方法的类型:
void ParseStackTrace(StackTrace trace)
{
[...]
while (ShouldlyFrame == null || currentFrame.GetMethod().IsShouldlyMethod())
{
if (currentFrame.GetMethod().IsShouldlyMethod())
ShouldlyFrame = currentFrame;
[...]
}
[...]
}
internal static bool IsShouldlyMethod(this MethodBase method)
{
if (method.DeclaringType == null)
return false;
return
method
.DeclaringType
.GetCustomAttributes( typeof(ShouldlyMethodsAttribute), true )
.Any()
||
(
method.DeclaringType.DeclaringType != null
&&
method
.DeclaringType
.DeclaringType
.GetCustomAttributes( typeof(ShouldlyMethodsAttribute), true )
.Any()
);
}
所以只需将 [ShouldlyMethods]
属性添加到我的扩展方法的容器中即可 类:
[ShouldlyMethods]
public static class ShouldlyAssertionExtensions
{
public static void ShouldBeValidBar( this IDictionary<String,Bar> dict, String barName )
{
dict.ShouldNotBeNull();
dict.TryGetValue( barName, out Bar bar ).ShouldBeTrue();
bar.ShouldNotBeNull();
bar.ChildList.Count.ShouldBe( expected: 3 );
bar.Message.ShouldBeNull();
}
}
现在我的断言错误具有 ShouldBeValidBar
的调用站点的上下文。万岁!