使用参数并行运行 nunit 测试 (nunit 3.8.x)

Posted

技术标签:

【中文标题】使用参数并行运行 nunit 测试 (nunit 3.8.x)【英文标题】:Run nunit tests parallel with parameters (nunit 3.8.x) 【发布时间】:2018-05-04 21:47:25 【问题描述】:

我想并行运行我的 selenium 测试并在我的 assembly.cs 中设置以下内容。

[assembly: Parallelizable(ParallelScope.Fixtures)]

好的,好的。这行得通。

这是代码结构的一个简短示例

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()
        
        
    

测试需要用户名和密码,并在网页中执行某些操作。登录等当前在 OneTimeSetUp 方法中处理。该网页将上次使用的视图保存在用户设置中。

如果我按顺序运行测试,我没有问题,因为测试不会相互影响。所有测试都可以使用相同的用户名运行。

如果我并行运行,它们可能会相互影响。比如test1配置了一个视图,test2中不应该看到的。

我的想法是为不同的用户运行这些课程(其中有很多)。当测试开始时,它应该使用一个用户名,当前并行运行的测试不使用该用户名。目前我不知道nunit并行运行了哪些测试,所以无法直接参数化。

我没有找到任何控制并行测试的方法。我可以定义是否并行以及并行执行的数量。我想要的是,提供并行运行测试参数。如果我有 3 个并行运行的测试类,我想给出所有 3 个不同的参数。

有什么想法可以实现吗?

【问题讨论】:

我会先阅读 NUnit 文档并尝试一些代码。完成后,如果它不起作用,请发布您尝试的内容和结果(以及任何错误消息)。 如果您能提供minimal reproducible example,那就太好了。 抱歉,不太容易描述。我希望它现在更容易理解了。 【参考方案1】:

如果使用单例模式从一组基于 threadid 的密码中分配呢?

快速解释,

IThreadCredentials 是一个描述凭据的接口,无论它们在您的情况下是什么样子。

ThreadCredentials 是我编写的一个实现 IThreadCredentials 的简单类。

ICredentialManager 是一个接口,用于描述如何分配和返回凭据。

CredentialManager.Instance 是在您的设备之间共享以借用和返回凭据的单例。

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);
            
        
    

用法:

在您的测试夹具设置中,您可以执行以下操作:

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

在拆解中,您可以

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

显然,在并行运行线程时,您至少需要有足够数量的可用凭据,否则在出队时会出现异常。

【讨论】:

【参考方案2】:

使用 nunit TestCase("data") 属性 示例:

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

【讨论】:

感谢 Magesh,我知道这个属性。问题是,如何控制哪个并行运行的实例获取哪个参数。由于 nunit “随机”启动并行测试,它可能会发生,并行测试使用相同的参数。这是我想避免的。【参考方案3】:

没有 nunit 的一个可能答案是提供参数的小服务。所以每个并行测试都应该调用一个 web 服务,并且会得到它唯一的参数。 Web 服务将在每次调用时返回下一个参数集。

如果我提供 10 个不同的参数集,并并行运行 3 个测试,我可以非常确定,3 个并行测试永远不会得到相同的参数。假设所有测试用例需要几乎相同的时间。

我将其称为 hack,因此我要求提供 nunit 解决方案。

【讨论】:

以上是关于使用参数并行运行 nunit 测试 (nunit 3.8.x)的主要内容,如果未能解决你的问题,请参考以下文章

Selenium:如何使用 NUnit 创建测试证据报告

如何迭代地运行 nunit 测试

运行 vstest.console.exe 时如何过滤 NUnit 测试

使用 NUnit 时,我应该如何在运行时修改 app.config?

通过 NUnit 运行 C++ 代码

在 xUnit.net 中测试参数化,类似于 NUnit