简体   繁体   中英

How do I Reset a Static Field (i.e. trigger it to be created again the next time the class is created)?

So here's a sample singleton:

public class Singleton
{
    private static Singleton _instance = new Singleton();

    private Singleton()
    {
        //do stuff
    }

    public static Singleton Get()
    {
        return _instance;
    }
}

I'm trying to run multiple tests on this singleton class, and the first one always works, naturally. Basically, I'm able to see that when I create a new Singleton() the constructor is called. Or, when I call Singleton.Get() the constructor is called. The tests work fine individually , but then the static field has been set, and even if I manually set it to null, it still won't reset itself for the next test. This is a problem for me as automated testing is important here . So how can I get it to reset itself?

So far, I researched AppDomain and found that I could put each test into its own assembly and run it manually. But of course, VS already uses a unique AppDomain for each assembly which would allow for that to happen automatically, but that's a LOT of test assemblies and seems a bit silly. I'm looking for an alternative to that.

Note: I don't want advice about code quality. This is code I'm testing, not that I've written. I will not change the code until I've tested it first. I researched this thoroughly and did not find a question about this that was not answered with "You shouldn't use singletons". I am not interested in code advice. This is StackOverflow, not CodeReview.

Additional Information Requested

I am running tests currently using Visual Studio's Built-In Testing framework. I would prefer that this still worked when it is moved to MSBuild. That doesn't rule out manually triggering an external application for the tests, but it does make it harder to do.

You're misunderstanding AppDomains.

You can simply create a new AppDomain for each test, load all of the test assemblies into it, and invoke only one test method in each domain.
It would probably be rather slow, though.

You can design your Singleton class like this:

public class Singleton
{
    private static Singleton _instance;
    private static object _instanceLock = new object();

    private Singleton()
    {
        //do stuff
    }

    public static Singleton Get()
    {
        if (_instance == null)
        {
            lock(_instanceLock)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
            }
        }

        return _instance;
    }

    public static void Clear()
    {
        if (_instance != null)
        {
            lock(_instanceLock)
            {
                if (_instance != null)
                {
                    _instance = null;
                }
            }
        }
    }
}

Then you'd have to call Singleton.Clear() before you begin each test, like this:

[TestInitialize]
public void Initialize()
{
    Singleton.Clear();
}

Write a console program that has a command-line argument to determine which of those singleton tests to run. Then call that multiple time from a batch file (or bash or powershell, whichever you prefer). It's extra work but it will let you test this code in a fresh new environment every time. Or you could try to figure out whether there's some option in your current tool to do that.

Perhaps something like that:

static int Main(string[] args) {
   try {
      int testcase = (Int32.Parse(args[0]));
      RunTest(testcase);
   } catch (Exception x) {
      Console.WriteLine("test failed: "+x.Message);
      return 1;
   }
   Console.WriteLine("test passed.");
   return 0;
}

After numerous bits of advice from @redtuna @drch and @SLaks and lots of googling we have determined a way to do this.

Step 1: Your test class needs to inherit from MarshalByRefObject

public class UnitTest1 : MarshalByRefObject

Step 2: This next piece is a custom method to run each test in it's own AppDomain .

private void RunTestInCustomDomain(string methodName)
{
    // selecting the dll from the debug directory using relative directories
    var testDll = @"..\..\..\UnitTests\bin\Debug\UnitTests.dll";
    // first verify the dll exists
    Assert.IsTrue(File.Exists(testDll));
    var assemblyName = AssemblyName.GetAssemblyName(testDll).FullName;
    var domain = AppDomain.CreateDomain(methodName, null, new AppDomainSetup()
    {
        // This is important, you need the debug directory as your application base
        ApplicationBase = Path.GetDirectoryName(testDll)
    });
    // create an instance of your test class
    var tests = domain.CreateInstanceAndUnwrap(assemblyName, typeof(UnitTest1).FullName) as UnitTest1;
    var type = tests.GetType();
    var method = type.GetMethod(methodName);
    // invoke the method inside custom AppDomain
    method.Invoke(tests, new object[]{});
    // Unload the Domain
    AppDomain.Unload(domain);
}

Step 3: The next trick is that your Test Method is calling this custom method, so your tests are written into public methods without the [TestMethod] attribute.

[TestMethod]
[DeploymentItem("UnitTests.dll")]
public void TestMethod1()
{
    RunTestInCustomDomain("actual_TestMethod1");
}

public void actual_TestMethod1()
{
    // Assert Stuff
}

For Completeness: If you need to run initialization or cleanup for each test, they need to be called manually because the TestMethod is running in a different AppDomain from the actual_TestMethod

public void actual_TestMethod1()
{
    // Assert Stuff
    TestCleanup();
}

Edit: I should note that because these methods are being run under a separate AppDomain, Code Coverage doesn't work :(. If anyone finds a way to get that functionality in addition to this feature, please let me know.

Just an idea:

Setting it to null just sets your variable to null instead of what it's pointing at, of course. But what if you could get the Singleton itself... Well, it seems that that can be done using Expression s. See Getting the object out of a MemberExpression?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM