I'm not sure if I'm relying on some behavior which is not guaranteed, so I'm not 100% sure if this is an issue.
I would like to have parallel testing on a class to speed up.
To make sure each worker initialize a separate instance of the specific class, I created a wrapper which returns the instance of the class under the current thread (based on Thread.CurrentThread.Name), or initialize a new instance if not initialized yet - so in the test class I would just call the wrapper without considering which thread I'm now on and which instance is the one I should get among a set of instances. It it something like:
public static class TestObjectWrapper
{
public static TestObject Get()
{
if (!testObjects.ContainsKey(Thread.CurrentThread.Name))
{
testObjects.AddOrUpdate(Thread.CurrentThread.Name, new TestObject(), (o, n) => new TestObject());
}
return testObjects[Thread.CurrentThread.Name];
}
private static ConcurrentDictionary<string, TestObject> testObjects = new ConcurrentDictionary<string, TestObject>();
}
So no matter where I am, I only need to call TestObjectWrapper.Get(), I can get an instance.
The reason why I rely on the thread name is because the wrapper is inside another project without NUnit reference, and it's also used in some other places without NUnit (so I cannot take TestContext.CurrentContext.WorkerId instead). Altogether, I read from stackoverflow that the thread name is identical to the worker id, which is something like ParallelWorker#8, or NonParallelWorker, or null (under Debug mode).
Under most scenarios it works quite well, when I only enable test fixture level parallelism ([Parallelizable(ParallelScope.Fixtures)]), but I found an unexpected behavior for OneTimeTearDown method in the test fixtures.
In the OneTimeTearDown, I'm hoping to get the class instance to do some clean up. So I have some code like:
[Parallelizable(ParallelScope.Fixtures)]
public class TestClass
{
// Actual test code omitted
[OneTimeTearDown]
public void OneTimeTearDown()
{
TestObjectWrapper.Get().Shutdown();
}
}
Obviously I'm expecting getting the same instance used by test methods in this test fixture, but later I found there's high chance that this method is exectued on a completely new thread, which caused the original instance not terminated but creates a new one instead.
And during my observation I found this only happens to OneTimeTearDown methods. The other types (e.g. OneTimeSetUp, SetUp, TearDown) are still always running in the same thread of the test methods.
Then I did some research - I output TestContext.CurrentContext.WorkerId and Thread.CurrentThread.Name, and found mostly they are identical, but sometimes in OneTimeTearDown, they are not the same. TestContext.CurrentContext.WorkerId is consistent among all the methods in the test fixture, and Thread.CurrentThread.Name is the same as WorkerId, unless in the OneTimeTearDown method.
An example of the output:
Consturctor Worker Id = ParallelWorker#7
Consturctor Thread Name = ParallelWorker#7
OneTimeSetUp Worker Id = ParallelWorker#7
OneTimeSetUp Thread Name = ParallelWorker#7
SetUp Worker Id = ParallelWorker#7
SetUp Thread Name = ParallelWorker#7
TestMethod1 Worker Id = ParallelWorker#7
TestMethod1 Thread Name = ParallelWorker#7
TearDown Worker Id = ParallelWorker#7
TearDown Thread Name = ParallelWorker#7
SetUp Worker Id = ParallelWorker#7
SetUp Thread Name = ParallelWorker#7
TestMethod2 Worker Id = ParallelWorker#7
TestMethod2 Thread Name = ParallelWorker#7
TearDown Worker Id = ParallelWorker#7
TearDown Thread Name = ParallelWorker#7
OneTimeTearDown Worker Id = ParallelWorker#7
OneTimeTearDown Thread Name = ParallelWorker#4 // Worker is the same, but thread is new, and name differs form Worker Id
This explains why I get a new instance other than the original one in the OneTimeTearDown method.
I thought I could have a workaround that assign the thread name from the worker id, so it can pretend it's still in the original thread and then then wrapper can return the expected instance, but later when I try to write some code like this:
[OneTimeTearDown]
public void OneTimeTearDown()
{
Thread.CurrentThread.Name = TestContext.CurrentContext.WorkerId; // Added this line
TestObjectWrapper.Get().Shutdown();
}
Then I found the code execution just terminates at the added line. The original shutdown line is not performed at all, and there's error message telling that - everything looks correct unless the code is not executed.
I read the documents, and tried with some attributes, like [SingleThreaded], or [RequiresThread], but they don't works as my thought - still the same behavior on the OneTimeTearDown method.
So, as a quick recap, my questions are listed below:
- Is the consistency between thread name and worker id, an officially supported behavior?
- Is there any way which can really make sure all the code in a test fixture (with
[Parallelizable(ParallelScope.Fixtures)]) executed in the same thread & worker?
- If 2 is not possible, if there is any way I can reassign the value of
Thread.CurrentThread.Name?
Environments:
Visual Studio 2019 Enterprise
NUnit 3.13.1
NUnit3TestAdaptor 3.17.0
Test project is targeting .NET Core 3.1
Running test from VS Test Explorer of Run mode (Debug mode seems ignoring parallism, which is fine)
Thanks.
I'm not sure if I'm relying on some behavior which is not guaranteed, so I'm not 100% sure if this is an issue.
I would like to have parallel testing on a class to speed up.
To make sure each worker initialize a separate instance of the specific class, I created a wrapper which returns the instance of the class under the current thread (based on Thread.CurrentThread.Name), or initialize a new instance if not initialized yet - so in the test class I would just call the wrapper without considering which thread I'm now on and which instance is the one I should get among a set of instances. It it something like:
So no matter where I am, I only need to call
TestObjectWrapper.Get(), I can get an instance.The reason why I rely on the thread name is because the wrapper is inside another project without NUnit reference, and it's also used in some other places without NUnit (so I cannot take TestContext.CurrentContext.WorkerId instead). Altogether, I read from stackoverflow that the thread name is identical to the worker id, which is something like ParallelWorker#8, or NonParallelWorker, or null (under Debug mode).
Under most scenarios it works quite well, when I only enable test fixture level parallelism ([Parallelizable(ParallelScope.Fixtures)]), but I found an unexpected behavior for OneTimeTearDown method in the test fixtures.
In the OneTimeTearDown, I'm hoping to get the class instance to do some clean up. So I have some code like:
Obviously I'm expecting getting the same instance used by test methods in this test fixture, but later I found there's high chance that this method is exectued on a completely new thread, which caused the original instance not terminated but creates a new one instead.
And during my observation I found this only happens to OneTimeTearDown methods. The other types (e.g. OneTimeSetUp, SetUp, TearDown) are still always running in the same thread of the test methods.
Then I did some research - I output TestContext.CurrentContext.WorkerId and Thread.CurrentThread.Name, and found mostly they are identical, but sometimes in OneTimeTearDown, they are not the same. TestContext.CurrentContext.WorkerId is consistent among all the methods in the test fixture, and Thread.CurrentThread.Name is the same as WorkerId, unless in the OneTimeTearDown method.
An example of the output:
This explains why I get a new instance other than the original one in the OneTimeTearDown method.
I thought I could have a workaround that assign the thread name from the worker id, so it can pretend it's still in the original thread and then then wrapper can return the expected instance, but later when I try to write some code like this:
Then I found the code execution just terminates at the added line. The original shutdown line is not performed at all, and there's error message telling that - everything looks correct unless the code is not executed.
I read the documents, and tried with some attributes, like
[SingleThreaded], or[RequiresThread], but they don't works as my thought - still the same behavior on the OneTimeTearDown method.So, as a quick recap, my questions are listed below:
[Parallelizable(ParallelScope.Fixtures)]) executed in the same thread & worker?Thread.CurrentThread.Name?Environments:
Visual Studio 2019 Enterprise
NUnit 3.13.1
NUnit3TestAdaptor 3.17.0
Test project is targeting .NET Core 3.1
Running test from VS Test Explorer of Run mode (Debug mode seems ignoring parallism, which is fine)
Thanks.