You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# NUnit中实现方法仅执行一次的方案排查——继承场景下[OneTimeSetUp]与[SetUpFixture]失效问题

Fixing NUnit's One-Time Setup for Cross-Inheritance Scenarios

Hey there! I get where you're coming from—switching from Java's Selenium annotations to NUnit can have a few gotchas, especially with suite-level setup. Let's figure out why your current approach isn't working and fix that duplicate folder creation issue.

Why Your Current Code Isn't Behaving as Expected

The problem with using [SetUpFixture] on your base Controls class is that NUnit treats each test fixture (your UnitTest1 and UnitTest2 classes) as separate suites when they inherit from a [SetUpFixture]-marked base. That means every time a subclass is initialized as a test fixture, it triggers the base class's [OneTimeSetUp]—hence your duplicate folder creation.

[SetUpFixture] is designed to be a standalone class for namespace-level or global suite setup, not a base class for inheritance. Let's adjust your structure to match NUnit's intended behavior.

The Correct Approach

We'll split your code into two distinct parts:

  1. A global suite-level setup class to handle the one-time folder creation.
  2. A regular base class for test-specific setup (like initializing drivers) that your test classes can inherit from.

Step 1: Create a Global Suite Setup Class

Make a standalone class marked with [SetUpFixture] in your root namespace. This will run once before any tests execute, regardless of how many test fixtures you have.

namespace Core
{
    [SetUpFixture]
    public class GlobalTestSuiteSetup
    {
        [OneTimeSetUp]
        public void BeforeSuite()
        {
            // Your logic to create the unique report folder goes here
            // This will execute exactly once when the test suite starts
            Console.WriteLine("Creating unique report folder - only runs once!");
        }

        [OneTimeTearDown]
        public void AfterSuite()
        {
            // Optional: Cleanup logic that runs once after all tests finish
        }
    }
}

Step 2: Adjust Your Controls Base Class

Turn Controls into a regular base class (no [SetUpFixture] or [TestFixture] attributes) that handles per-test or per-fixture setup/teardown for your drivers.

namespace Core.Framework.Controls
{
    // No [SetUpFixture] or [TestFixture] here—this is a plain base class
    public class Controls
    {
        protected IWebDriver _driver;
        protected string BrowserName { get; set; }

        // Per-test setup (runs before each test method)
        public void SetUpDriver(string browser)
        {
            BrowserName = browser;
            // Your driver initialization logic here (e.g., ChromeDriver, FirefoxDriver)
            // Example: _driver = new ChromeDriver();
        }

        // Per-test teardown (runs after each test method)
        [TearDown]
        public void TearDownDriver()
        {
            _driver?.Quit();
        }

        // Your browser test case source
        public static IEnumerable<string> BrowsersToRunWith()
        {
            yield return "Chrome";
            yield return "Firefox";
        }
    }
}

Step 3: Update Your Test Classes

Your test classes remain mostly the same—they inherit from Controls and run their tests as before. The global setup will handle the one-time folder creation automatically.

namespace Core
{
    [TestFixture]
    [Parallelizable]
    public class UnitTest1 : Controls
    {
        [Test]
        [TestCaseSource(typeof(Controls), "BrowsersToRunWith")]
        public void Bing(string browser)
        {
            SetUpDriver(browser);
            Class1 class1 = new Class1(_driver);
            class1.LaunchURL("http://bing.com");
            class1.EnterTextInBing("Parallel Execution using C#");
            Thread.Sleep(3000);
        }
    }
}
namespace Core
{
    [TestFixture]
    [Parallelizable]
    public class UnitTest2 : Controls
    {
        [Test]
        [TestCaseSource(typeof(Controls), "BrowsersToRunWith")]
        public void Google(string browser)
        {
            SetUpDriver(browser);
            Class1 class1 = new Class1(_driver);
            class1.LaunchURL("http://google.com");
        }
    }
}

Key Notes to Ensure This Works

  • Namespace Placement: The [SetUpFixture] class should be in the root namespace (like Core) to apply to all test classes in that namespace and its sub-namespaces. If you put it in a sub-namespace, it will only apply to tests within that sub-namespace.
  • No Inheritance for SetUpFixture: Never inherit from a [SetUpFixture] class—this is what caused your original duplicate execution issue. Keep it standalone.
  • Parallel Safety: The global [OneTimeSetUp] runs before any parallel tests start, so your folder creation logic is safe from race conditions.

内容的提问来源于stack exchange,提问作者Naseem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 15:22:41