【问题标题】:How can I have custom asserts with Shouldly and maintain the call-site-specific assertion messages?如何使用 Shouldly 进行自定义断言并维护特定于调用站点的断言消息?
【发布时间】:2020-06-03 19:08:34
【问题描述】:

我在我的 xUnit 测试中使用the excellent Shouldly library,并且我发现自己在不同的测试中使用了设置的断言序列,所以我将它们组合成新的断言扩展方法——但是当我这样做时,我失去了@ 987654323@ 的上下文断言消息。

这是我的旧代码,它与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" );
}

...但是现在我的应该断言消息不包含来自 Dict_should_contain_valid_Foobar_Bar_entry 的任何上下文信息,而只包含来自 ShouldBeValidBar 的上下文。

我如何指示Shouldly 忽略ShouldBeValidBar 的上下文并改用其父调用站点?

【问题讨论】:

    标签: c# unit-testing assert xunit shouldly


    【解决方案1】:

    TL;DR:

    [ShouldlyMethods] 属性添加到您的自定义断言扩展方法(不是单独的扩展方法):

    [ShouldlyMethods] // <-- This, right here!
    public static class MyShouldlyAssertions
    {
        public static void ShouldBeValidBar( this IDictionary<String,Bar> dict, String barName )
        {
            [...]
        }
    }
    

    长版:

    经过一番谷歌搜索,and reading articles about how Shouldly works - 并在阅读the source of Shouldly's secret-sauce: SourceCodeTextGetter 之后,我看到它确定了堆栈跟踪中的哪些条目可以被忽略 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 调用站点的上下文。万岁!

    【讨论】:

    • 感谢您深入研究!我无法在扩展方法本身上使用[ShouldlyMethods],但是当我将属性添加到包含类时它可以工作。
    • @NielsvanderRest 如果它不适用于您的扩展方法,那就太奇怪了。您是否对解决方案进行了干净+重建?反思说什么?您是否多次引用了不同版本的 Shouldly?
    • 我使用的是最新版本 (3.0.2),它似乎只测试 method.DeclaringType 上的属性,而不是方法本身 (source)。这可以解释我所看到的行为。它似乎与您发布的代码相同。实际上,我被触发将属性放在类上,因为这是我在 Shouldly 的源代码中看到的。
    • @NielsvanderRest 哇哦!好发现!我只是查看了自己的代码,发现我将[ShouldlyMethods] 应用于我的扩展方法类,而不是方法。我现在会更新我的答案。
    • 请注意:如果断言方法为async,则此方法不起作用。在这种情况下,Shouldly 将无法获取 should 表达式。
    猜你喜欢
    • 2016-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多