更改 NUnit 测试的名称

Change name of NUnit test

我希望基于 NUnit 框架的单元测试在 Visual Studio 测试资源管理器中命名为更易于阅读。

例如,与其使用 Test_Case_1TestCase1,不如使用 Test Case #1, Category: First, Category: Second 之类的东西(通过从 [Category] 属性分配值),使用空格和方法名称中不允许使用的字符。

我知道它在 xUnit 中是开箱即用的,但我不能涉及它,因为我正在使用我无法使用 xUnit 框架实现的自定义.

是否可以用 NUnit 重写单元测试显示名称?到目前为止,我可以看到,TestDetailFullName 字段具有私有 setter.

是否有任何其他方式或方法更改 NUnit 测试的显示名称?

如果您使用参数化测试,则支持此功能,您可以在添加 TestCase 属性时指定 TestName

如果您不使用 TestCase,那么您可以将其用作不太理想的变通方法来实现您想要做的事情。所以你会像这样声明你的测试:

[TestCase(null,TestName="Test Case #1, Category: First, Category: Second")]
public void TestCase(object ignored)

这并不理想,因为它不是程序化的,所以您必须手动键入测试名称,而不是从测试方法的属性中生成它。您还必须向该方法传递一个参数,这就是 ignorednull 的意义所在。当然,您可以开始使用参数化测试,在这种情况下,您将向测试传递实际值。

[TestCase(5,TestName="Test Case #1, Category: First, Category: Second")]
public void TestCase(int someInput) {
    Assert.AreEqual(5, someInput);
}

您可以创建自己的名称属性:

// I used the same namespace for convenience
namespace NUnit.Framework
{
    public class NameAttribute  : NUnitAttribute, IApplyToTest
    {
        public NameAttribute(string name)
        {
            Name = name;
        }

        public string Name { get; set; }

        public void ApplyToTest(Test test)
        {
            test.Properties.Add("Name", Name);
        }
    }
}

然后您可以像使用常规 NUnit 一样使用它 属性(就像类别和描述一样)。

[Test, Name("My Awesome Test"), Category("Cool.Tests"), Description("All cool tests")]
public void Test313()
{
    // Do something
}

您可以在您的 TestContext 中看到数据:

if (TestContext.CurrentContext.Test.Properties.ContainsKey("Name"))
{
    name = TestContext.CurrentContext.Test.Properties.Get("Name") as string;
}

我想以编程方式动态更改参数化 NUnit 测试的测试名称,即基于测试数据文件夹中的输入文件。

进行了几次调整,但效果很好:

[Test, TestCaseSource(nameof(GetSites))]
public void TestForEveryFile(object ignored, FileInfo testFile) {
   // actual test using 'testFile'
}

public static IEnumerable<TestCaseData> GetSites()
{
    foreach (string testFile in Directory.EnumerateFiles("TestData"))
    {
        FileInfo fileInfo = new FileInfo(testFile);

        // Pass '' as first argument to TestCaseData to suppress the default test name
        // (seems to be necessary even if TestName is set)
        var testCase = new TestCaseData("", fileInfo) 
        {
            // Use the short file name as test name.
            // As dots (.) would indicate a test hierarchy, we replace them with '-'.
            TestName = fileInfo.Name.Replace(".", "-")
        };
        yield return testCase;
    }
}

一种可能的方法是创建您自己的 TestAttribute class 并将 NUnit.Framework.Internal.TestMethod 实例的 属性 Name 设置为您想要的任何文本显示(参见下面代码中的方法 TestAttribute.BuildFrom)。

请注意,此代码只不过是一个 hack。可能会引起我没有想到的副作用。

using System;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using NUnit.Framework.Internal.Builders;

namespace NUnitTesting.Tests
{
    [TestFixture(TestName = "Display name of Tests")]
    public class Tests
    {
        [Test(DisplayName = "Display name of Test1")]
        public void Test1()
        {
        }
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public class TestAttribute : NUnitAttribute, ISimpleTestBuilder, IApplyToTest, IImplyFixture
    {
        /// <summary>
        /// The author of this test.
        /// </summary>
        public String Author { get; set; }

        /// <summary>
        /// Descriptive text for this test.
        /// </summary>
        public String Description { get; set; }

        /// <summary>
        /// The display name for this test.
        /// </summary>
        public String DisplayName { get; set; }

        /// <summary>
        /// Gets or sets the expected result.
        /// Not valid if the test method has parameters.
        /// </summary>
        /// <value>The result.</value>
        public Object ExpectedResult
        {
            get => this.expectedResult;
            set
            {
                this.expectedResult = value;
                this.hasExpectedResult = true;
            }
        }

        /// <summary>
        /// The type that this test is testing.
        /// </summary>
        public Type TestOf { get; set; }

        #region IApplyToTest Members

        /// <summary>
        /// Modifies a test by adding a description, if not already set.
        /// </summary>
        /// <param name="test">The test to modify.</param>
        public void ApplyToTest(Test test)
        {
            if (!(test.Method is Object))
            {
                throw new ArgumentException("This attribute must only be applied to tests that have an associated method.", nameof(test));
            }

            if (!test.Properties.ContainsKey(PropertyNames.Description) && this.Description != null)
                test.Properties.Set(PropertyNames.Description, this.Description);

            if (!test.Properties.ContainsKey(PropertyNames.Author) && this.Author != null)
                test.Properties.Set(PropertyNames.Author, this.Author);

            if (!test.Properties.ContainsKey(PropertyNames.TestOf) && this.TestOf != null)
                test.Properties.Set(PropertyNames.TestOf, this.TestOf.FullName);

            if (this.hasExpectedResult && test.Method.GetParameters().Length > 0)
                test.MakeInvalid("The 'TestAttribute.ExpectedResult' property may not be used on parameterized methods.");
        }

        #endregion


        #region ISimpleTestBuilder Members

        /// <summary>
        /// Builds a single test from the specified method and context.
        /// </summary>
        /// <param name="method">The method for which a test is to be constructed.</param>
        /// <param name="suite">The suite to which the test will be added.</param>
        public TestMethod BuildFrom(IMethodInfo method, Test suite)
        {
            TestCaseParameters parms = null;

            if (this.hasExpectedResult)
            {
                parms = new TestCaseParameters();
                parms.ExpectedResult = this.ExpectedResult;
            }

            var testMethod = this.builder.BuildTestMethod(method, suite, parms);
            testMethod.Name = this.DisplayName;
            return testMethod;
        }

        #endregion

        private readonly NUnitTestCaseBuilder builder = new NUnitTestCaseBuilder();

        private Object expectedResult;
        private Boolean hasExpectedResult = false; // needed in case result is set to null
    }
}

这样,至少 Visual Studio 测试资源管理器显示“Test1 的显示名称”而不仅仅是“Test1”:

但是 ReSharper 的测试 runner/explorer 仍然使用该方法的名称:

除了上面的@forsvarir 回答之外,以下内容现在有效:

[TestCase(TestName = "Test case name")]
Public void TestSomething()
{
   ...
}