NUnit 有条件拆卸?

发布于 2024-07-30 09:24:08 字数 174 浏览 3 评论 0原文

有没有办法在 NUnit 中进行有条件的 TearDown?

我有一个 TestFixture,它只需要为几个测试运行清理代码,但我真的不想:

  1. 在每个测试上运行 TearDown 方法
  2. 创建一个私有帮助器方法,如果可以的话,从需要清理的测试中调用它躲开它

Is there a way to do a conditional TearDown in NUnit?

I have a TestFixture which has a need to run cleanup code for just a few tests, and I don't really want to:

  1. Run the TearDown method on every test
  2. Create a private helper method and call it from the tests requiring cleanup if I can avoid it

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

梦里寻她 2024-08-06 09:24:08

不幸的是没有。

当所有测试完成后,您是否可以不在 [TestFixtureTearDown] 中进行清理? 我想这取决于是否必须在下一次测试运行之前完成清理。

或者,将需要清理的测试放在另一个类/TextFixture 中,远离其他测试。 然后您可以在其中使用不需要条件的 TearDown。

编辑:
我刚刚想到的一件事是,您可以扩展 NUnit - 创建您自己的自定义属性,您可以根据需要处理这些属性,尽管对于这种特殊需求来说可能实际上并不值得。 此处提到了这一点 。 就像我说的,我认为你不应该为此走这条路,但了解这一点还是很有用的

There isn't unfortunately.

Can you not do the cleanup in the [TestFixtureTearDown] instead, so once all the tests have finished? I guess that depends on whether the cleanup has to be done before the next test runs.

Alternatively, put those tests that require a cleanup in another class/TextFixture together, away from the other tests. Then you can use a TearDown in there which doesn't need to be conditional.

Edit:
One thing I've just thought of, which could be done to achieve the aim though probably isn't actually worth it for this particular need, is that you can extend NUnit - create your own custom attributes which you could handle however you wanted. This is mentioned here. Like I say, I don't think really you should go down that route for this, but is useful to know none-the-less

氛圍 2024-08-06 09:24:08

您可以将主 TearDown 放在基类中:

[TearDown]
public virtual void TearDown()
{
  // Tear down things here
}

然后在具有不应运行拆卸代码的测试的类中覆盖它:

[TearDown]
public override void TearDown()
{
  // By not calling base.TearDown() here you avoid tearing down
}

You can have the main TearDown in a base class:

[TearDown]
public virtual void TearDown()
{
  // Tear down things here
}

and then override it in the class where you have the tests that should not run the tear down code:

[TearDown]
public override void TearDown()
{
  // By not calling base.TearDown() here you avoid tearing down
}
小兔几 2024-08-06 09:24:08

使用BaseTest 中的测试扩展所有类,

    public class BaseTest
    {
        [SetUp]
        public void BeforeTest()
        {
            GetService<NUnitHooksController>().ExecuteBeforeTestHooks(this);
        }

        [TearDown]
        public void AfterTest()
        {
            GetService<NUnitHooksController>().ExecuteAfterTestHooks(this);
        }
    }

使用 AfterTest 和 BeforeTest 挂钩。 无论有类别还是无类别都适用。

    public class ExampleTest : BaseTest
    {
        [Test, Category("asdasd")]
        public void Test01()
        {
           ...
        }

        [AfterTest("asdasd")]
        public void ExampleHook()
        {
           ...
        }

    }
    public class NUnitHooksController
    {
        private readonly ILogger _log;

        public NUnitHooksController(ILogger log)
        {
            _log = log;
        }

        public void ExecuteBeforeTestHooks(object testClass)
        {
            ExecuteHooks(testClass, typeof(BeforeTestAttribute));
        }

        public void ExecuteAfterTestHooks(object testClass)
        {
            ExecuteHooks(testClass, typeof(AfterTestAttribute));
        }

        private MethodInfo[] GetHookMethods(object currentTestClass, Type attributeType)
        {
            return currentTestClass
                .GetType()
                .GetMethods()
                .Where(m => m.GetCustomAttributes(attributeType, false).Length > 0)
                .ToArray();
        }

        private void ExecuteHooks(object testClass, Type requiredAttributeType)
        {
            var hooks = GetHookMethods(testClass, requiredAttributeType);
            var testCategories = GetTestCategories();

            foreach (var hook in hooks)
            {
                var allAttributes = hook.GetCustomAttributes(requiredAttributeType, true);
                foreach (var attribute in allAttributes)
                {
                    if (!attribute.GetType().IsEquivalentTo(requiredAttributeType))
                    {
                        continue;
                    }

                    var hookCategories = GetCategoriesFromAttribute(attribute);

                    // if we do not have specific category on hook
                    // or we have at least one same category on hook and test
                    if (!hookCategories.Any() || hookCategories.Intersect(testCategories).Any())
                    {
                        ExecuteHookMethod(testClass, hook);
                    }
                }
            }
        }

        private object[] GetTestCategories()
        {
            return TestContext.CurrentContext.Test.Properties["Category"].ToArray();
        }

        private void ExecuteHookMethod(object testClass, MethodInfo method)
        {
            var hookName = method.Name;
            _log.Information($"Executing - '{hookName}' hook");

            try
            {
                method.Invoke(testClass, Array.Empty<object>());
            }
            catch (Exception e)
            {
                _log.Error($"Executing of - '{hookName}' hook failed - {e}");
            }
        }

        private string[] GetCategoriesFromAttribute(object attribute)
        {
            if (attribute is BeforeTestAttribute beforeTestAttribute)
            {
                return beforeTestAttribute.Categories;
            }

            if (attribute is AfterTestAttribute afterTestAttribute)
            {
                return afterTestAttribute.Categories;
            }

            throw new ArgumentException($"{attribute.GetType().FullName} - does not have categories");
        }
    }

Extend all you classes with test from BaseTest

    public class BaseTest
    {
        [SetUp]
        public void BeforeTest()
        {
            GetService<NUnitHooksController>().ExecuteBeforeTestHooks(this);
        }

        [TearDown]
        public void AfterTest()
        {
            GetService<NUnitHooksController>().ExecuteAfterTestHooks(this);
        }
    }

Use AfterTest and BeforeTest hooks. Works both with and without category.

    public class ExampleTest : BaseTest
    {
        [Test, Category("asdasd")]
        public void Test01()
        {
           ...
        }

        [AfterTest("asdasd")]
        public void ExampleHook()
        {
           ...
        }

    }
    public class NUnitHooksController
    {
        private readonly ILogger _log;

        public NUnitHooksController(ILogger log)
        {
            _log = log;
        }

        public void ExecuteBeforeTestHooks(object testClass)
        {
            ExecuteHooks(testClass, typeof(BeforeTestAttribute));
        }

        public void ExecuteAfterTestHooks(object testClass)
        {
            ExecuteHooks(testClass, typeof(AfterTestAttribute));
        }

        private MethodInfo[] GetHookMethods(object currentTestClass, Type attributeType)
        {
            return currentTestClass
                .GetType()
                .GetMethods()
                .Where(m => m.GetCustomAttributes(attributeType, false).Length > 0)
                .ToArray();
        }

        private void ExecuteHooks(object testClass, Type requiredAttributeType)
        {
            var hooks = GetHookMethods(testClass, requiredAttributeType);
            var testCategories = GetTestCategories();

            foreach (var hook in hooks)
            {
                var allAttributes = hook.GetCustomAttributes(requiredAttributeType, true);
                foreach (var attribute in allAttributes)
                {
                    if (!attribute.GetType().IsEquivalentTo(requiredAttributeType))
                    {
                        continue;
                    }

                    var hookCategories = GetCategoriesFromAttribute(attribute);

                    // if we do not have specific category on hook
                    // or we have at least one same category on hook and test
                    if (!hookCategories.Any() || hookCategories.Intersect(testCategories).Any())
                    {
                        ExecuteHookMethod(testClass, hook);
                    }
                }
            }
        }

        private object[] GetTestCategories()
        {
            return TestContext.CurrentContext.Test.Properties["Category"].ToArray();
        }

        private void ExecuteHookMethod(object testClass, MethodInfo method)
        {
            var hookName = method.Name;
            _log.Information(
quot;Executing - '{hookName}' hook");

            try
            {
                method.Invoke(testClass, Array.Empty<object>());
            }
            catch (Exception e)
            {
                _log.Error(
quot;Executing of - '{hookName}' hook failed - {e}");
            }
        }

        private string[] GetCategoriesFromAttribute(object attribute)
        {
            if (attribute is BeforeTestAttribute beforeTestAttribute)
            {
                return beforeTestAttribute.Categories;
            }

            if (attribute is AfterTestAttribute afterTestAttribute)
            {
                return afterTestAttribute.Categories;
            }

            throw new ArgumentException(
quot;{attribute.GetType().FullName} - does not have categories");
        }
    }
很糊涂小朋友 2024-08-06 09:24:08

我已经使用测试名称解决了这个问题:

namespace TestProject
{
    public class TestClass
    {
        // Test without TearDown
        [Test]
        public void Test1()
        {
            Assert.Pass("Test1 passed");
        }

        // Test with TearDown
        [Test]
        public void Test2()
        {
            Assert.Pass("Test2 passed");
        }

        [TearDown]
        public void TearDown()
        {
            // Execute only after Test2
            if (TestContext.CurrentContext.Test.Name.Equals(nameof(this.Test2)))
            {
                // Execute Test2 TearDown...
            }
        }
    }
}

或者,如果您想使用 Test2 (TestProject.TestClass.Test2) 的全名,您可以将该行替换

if (TestContext.CurrentContext.Test.Name.Equals(nameof(this.Test2)))

if (TestContext.CurrentContext.Test.FullName.Equals(typeof(TestClass).FullName + "." nameof(this.Test2)))

I have solved this using the name of the test:

namespace TestProject
{
    public class TestClass
    {
        // Test without TearDown
        [Test]
        public void Test1()
        {
            Assert.Pass("Test1 passed");
        }

        // Test with TearDown
        [Test]
        public void Test2()
        {
            Assert.Pass("Test2 passed");
        }

        [TearDown]
        public void TearDown()
        {
            // Execute only after Test2
            if (TestContext.CurrentContext.Test.Name.Equals(nameof(this.Test2)))
            {
                // Execute Test2 TearDown...
            }
        }
    }
}

Or if you want to use the full name of Test2 (TestProject.TestClass.Test2) you can replace the line

if (TestContext.CurrentContext.Test.Name.Equals(nameof(this.Test2)))

by

if (TestContext.CurrentContext.Test.FullName.Equals(typeof(TestClass).FullName + "." nameof(this.Test2)))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文