Skip to content

xunit.net 一个简单的忽略测试都做不好 #56

@ohroy

Description

@ohroy

需求

这是一个非常符合人类的逻辑的操作:忽略某些测试,除非我手动运行。
非常合理对吧?
对应当你测试一些有昂贵操作的行为时,这非常有用。比如你不能每次测试都请求某个收费的api吧,这样不光是效率问题,经费问题,还有可能因为网络的波动而导致测试失败。又比如当你在测试里写一些概念验证,比如往临时往数据库里插入某条数据,你总不可能每次测试都要运行吧。
这么一个简单的需求,xunit.net竟然选择了一个匪夷所思的实现方法,即彻底隐藏,无论如何也不能执行了。并且从2015年到现在(2021年)也坚持不改,大有一幅你打死我我也不改的架势。
这是一个多么愚蠢的设计,如果我真的需要永久的禁止这个测试,我直接删除掉不就行了,或者直接简单注释掉声明测试的注解,也要比这个方便的多。

        [Fact]
        public void TestS() {
            1.Should().Be(1);
        }

这样的一个测试,是改成

        [Fact(Skip = "!2313")]
        public void TestS() {
            1.Should().Be(1);
        }

容易,还是改成

        //[Fact]
        public void TestS() {
            1.Should().Be(1);
        }

容易? 我想答案显然易见。
而以前的Nunit则可以通过Explicit 注解完美实现。‘

怎么办

万万没想到,最后我还是用一个非常曲折的方式实现了这一需求

    public class RunnableInDebugOnlyAttribute : FactAttribute {
        public RunnableInDebugOnlyAttribute() {
            if (!Debugger.IsAttached) {
                Skip = "Only running in interactive mode.";
            }
        }
    }

    /// <summary>
    /// Apply this attribute to your test method to specify a category.
    /// </summary>
    [TraitDiscoverer("Test.CategoryDiscoverer", "Test")]
    [AttributeUsage(AttributeTargets.Method)]
    class IgnoredAttribute : Attribute, ITraitAttribute {
        public IgnoredAttribute() {
        }
    }

    /// <summary>
    /// Apply this attribute to your test method to specify a category.
    /// </summary>
    [TraitDiscoverer("Test.CategoryDiscoverer", "Test")]
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    class CategoryAttribute : Attribute, ITraitAttribute {
        public CategoryAttribute(string category) {
        }
    }

    /// <summary>
    /// This class discovers all of the tests and test classes that have
    /// applied the Category attribute
    /// </summary>
    public class CategoryDiscoverer : ITraitDiscoverer {
        /// <summary>
        /// Gets the trait values from the Category attribute.
        /// </summary>
        /// <param name="traitAttribute">The trait attribute containing the trait values.</param>
        /// <returns>The trait values.</returns>
        public IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute) {
            var ctorArgs = traitAttribute.GetConstructorArguments().ToList();
            var category = "";
            if (ctorArgs.Count == 0) {
                var name = traitAttribute.ToString()?.Split(".").Last();
                if (name.EndsWith("Attribute")) {
                    name = name.Remove(name.LastIndexOf("Attribute", StringComparison.Ordinal));
                }

                category = name;
            }
            else if (ctorArgs.Count == 1) {
                category = ctorArgs[0].ToString();
            }
            else {
                throw new Exception("unknown trait");
            }

            yield return new KeyValuePair<string, string>("Category", category);
        }
    }

尽管这种方式非常的麻烦

        [RunnableInDebugOnly, Ignored]
        public void TestS() {
            1.Should().Be(1);
        }

他需要配合Rider使用,在编辑器中设置Skip tests from categories 的值为 Ignored,即可在Rider中实现忽略掉指定的分类。
但是命令行怎么办?则使用 RunnableInDebugOnly注解来声明仅在Debug模式下执行。(当然也可以修改逻辑为某个环境变量存在时执行等)

结论

有些人吧,就是犟。臭毛病。

ref

xunit/xunit#701
https://youtrack.jetbrains.com/issue/RIDER-49097

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions