简体   繁体   中英

Run nunit tests parallel with parameters (nunit 3.8.x)

I want to run my selenium tests in parallel and set the following in my assembly.cs.

[assembly: Parallelizable(ParallelScope.Fixtures)]

Ok, fine. That works.

Here is a short example of the code structure

using NUnit.Framework;

namespace MyExample
{
    [TestFixture]
    [Category("TestsRunningWithLogin1")]
    public class Test_Example1
    {
        [Test, Order(1)]
        public void Test1()
        {
        }
        [Test, Order(2)]
        public void Test2()
        {
        }
    }

    [TestFixture]
    [Category("TestsRunningWithLogin2")]
    public class Test_Example2
    {
        [Test, Order(1)]
        public void Test1()
        {
        }
        [Test, Order(2)]
        public void Test2()
        {
        }
    }
}

The tests require a username and password and do something in a web page. The login etc. is currently handled in a OneTimeSetUp method. The webpage saves the last used view in user settings.

If I run the tests sequentially, I have no problems, due to the tests do not influence each other. All tests can run with the same username.

If I run it parallel, they could influence each other. For example test1 configures a view, what should not be seen in test2.

My idea was to run the classes (and there are a lot of them) with different users. When the test starts, it should take a username, which is currently not used by the parallel runing tests. Currently I don't know which tests are running in parallel by nunit, so I cannot parameterize them directly.

I did not find anything how to control the parallel tests. I can define, if parallel or not and how many executed in parallel. What I want is, to give the parallel running tests parameters. If I have 3 parallel running test classes, I want to give all 3 different parameters.

Any ideas how to achieve that?

What about using a singleton pattern to allocate from a set of passwords based on the threadid.

A quick explanation,

IThreadCredentials is an interface to describe the credentials, whatever they look like in your case.

ThreadCredentials is a simple class I have written that implements IThreadCredentials.

ICredentialManager is an interface to describe how credentials can be allocated and returned.

CredentialManager.Instance is the singleton that is shared amongst your fixtures to borrow and return the credentials.

public interface IThreadCredentials
{
    string UserName { get; }

    string Password { get; }
}

public class ThreadCredentials : IThreadCredentials
{
    public ThreadCredentials(string userName, string password)
    {
        this.UserName = userName;
        this.Password = password;
    }

    public string UserName { get; }

    public string Password  { get; }
}

public interface ICredentialManager
{
    IThreadCredentials GetCredentialsFromPool();
    void ReturnCredentialsToPool();
}

public sealed class CredentialManager : ICredentialManager
{
    private static readonly Lazy<CredentialManager> lazy = new Lazy<CredentialManager>(() => new CredentialManager());
    private static readonly object syncRoot = new object ();
    private static readonly Queue<IThreadCredentials> availableCredentialQueue = new Queue<IThreadCredentials>();
    private static readonly IDictionary<int, IThreadCredentials> credentialsByThread = new Dictionary<int, IThreadCredentials>();

    private CredentialManager()
    {
        IEnumerable<IThreadCredentials> availableCredentials = new[]{new ThreadCredentials("Foo", "FooPassword"), new ThreadCredentials("Bar", "BarPassword")};
        foreach (IThreadCredentials availableCredential in availableCredentials)
        {
            availableCredentialQueue.Enqueue(availableCredential);
        }
    }

    public static CredentialManager Instance => lazy.Value;

    public IThreadCredentials GetCredentialsFromPool()
    {
        return GetCredentialsFromPool(Thread.CurrentThread.ManagedThreadId);
    }

    public void ReturnCredentialsToPool()
    {
        ReturnCredentialsToPool(Thread.CurrentThread.ManagedThreadId);
    }

    private static IThreadCredentials GetCredentialsFromPool(int threadId)
    {
        lock (syncRoot)
        {
            IThreadCredentials result;
            if (credentialsByThread.TryGetValue(threadId, out result))
            {
                return result;
            }

            // This presupposes you have enough credentials for the concurrency you are permitting 
            result = availableCredentialQueue.Dequeue();
            credentialsByThread.Add(threadId, result);
            return result;
        }
    }

    private static void ReturnCredentialsToPool(int threadId)
    {
        lock (syncRoot)
        {
            if (credentialsByThread.ContainsKey(threadId))
            {
                IThreadCredentials credentials = credentialsByThread[threadId];
                credentialsByThread.Remove(threadId);
                availableCredentialQueue.Enqueue(credentials);
            }
        }
    }
}

Usage:

In your test fixture setup, you can do something like:

IThreadCredentials credentials = CredentialManager.Instance.GetCredentialsFromPool();
// Now you can use credentials for whatever

In the teardown, you can then

CredentialManager.Instance.ReturnCredentialsToPool();
// Then promise you stop using those credentials

Obviously, you will need to have at least the number of credentials available as you intend on running threads in parallel or you will get an exception on dequeue.

Use nunit TestCase("data") attribute Example:

 [TestCase("differentUserName", "password")]
 public void MyTest(string username, string password)          
 {  
   // Test steps
  }

One possible answer without nunit would be a little service, which delivers the parameters. So each parallel test should call a webservice and will get its unique parameters. The webservice would return with each call the next parameter set.

If I would provide 10 different parameter sets, and run 3 tests in parallel, I can quite be sure, that the 3 parallel tests never get the same parameters. Assumed that all test cases needs nearly the same time.

I would call this a hack, therefore I'm asking for a nunit solution.

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