1

我有一个测试,我想确保一页内的单独操作会产生不同的结果。具体来说,我有几种方法可以在页面上进行排序,并且我想要进行测试以确保每种排序都不同。我还有其他测试来确保每种类型的正确性。

我希望这次对话的重点是并行运行测试操作并在最后比较结果的好方法,而不是测试什么或测试方法。我认为测试中的并行操作是一个有趣且足够广泛的主题,它可能对其他人有用。

让“generateHashFromSearchResults()”是一个函数,它返回一个字符串,表示当前 IE 实例上显示的搜索结果的顺序。以下是使用一个浏览器实例以序列化方式运行的代码:

var set = new HashSet<string>();
var sortOptions = new List<String>() { "sort1", "sort2", "sort3" };

// Default sort
set.Add(generateHashFromSearchResults());

sortOptions.ForEach(s => {
  ie.Link(Find.ByText(s)).Click();
  set.Add(generateHashFromSearchResults());
});

Assert.That(set.Count() == 4);

几个月前我读过 PLINQ 并认为这可能是一个不错的用例。现在让“generateHashFromSearchResults(IE ie)”成为同一个函数,但它在明​​确定义的 IE 实例上运行。我试过这样的事情:

List<string> resultList = sortOptions.AsParallel().Select(s => {
  var ie = new IE(true);
  ie.Link(Find.ByText(s)).Click();
  return generateHashFromSearchResults(ie);
}).ToList();

// Forget about default sort for now. There should be 3 distinct results
Assert.That(new HashSet<string>(resultList).Count() == 3);

我现在面临的最大问题是不了解 PLINQ 如何进行线程管理。WatiN 需要在单元状态设置为单线程 (STAThread) 的情况下运行。我知道每个 IE 实例都应该在它自己的线程中,但是将 PLINQ 查询中的每个线程设置为正确的单元状态并不能解决问题。

我开始怀疑我需要了解更多关于 PLINQ 的信息才能继续,或者我需要手动学习更多关于线程管理的信息才能让它工作。

有什么想法吗?

4

2 回答 2

2

您不能使用 AsParallel() 指定自定义调度程序。但是您可以为每个排序选项创建一个任务,并将自定义调度程序的一个实例传递给 Start() 方法。这个 STA 线程调度程序的实现是从 Stephen Toub (http://blogs.msdn.com/b/pfxteam/archive/2010/04/07/9990421.aspx) 那里借来的:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
///     Provides a scheduler that uses STA threads.
///     Borrowed from Stephen Toub's implementation http://blogs.msdn.com/b/pfxteam/archive/2010/04/07/9990421.aspx
/// </summary>
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
    /// <summary>
    ///     The STA threads used by the scheduler.
    /// </summary>
    private readonly List<Thread> threads;

    /// <summary>
    ///     Stores the queued tasks to be executed by our pool of STA threads.
    /// </summary>
    private BlockingCollection<Task> tasks;

    /// <summary>
    ///     Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.
    /// </summary>
    /// <param name = "numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
    public StaTaskScheduler(int numberOfThreads)
    {
        if (numberOfThreads < 1)
        {
            throw new ArgumentOutOfRangeException(
                "numberOfThreads", "The scheduler must create at least one thread");
        }

        // Initialize the tasks collection
        this.tasks = new BlockingCollection<Task>();

        // Create the threads to be used by this scheduler
        this.threads = Enumerable.Range(0, numberOfThreads).Select(
            i =>
                {
                    var thread = new Thread(
                        () =>
                            {
                                // Continually get the next task and try to execute it.
                                // This will continue until the scheduler is disposed and no more tasks remain.
                                foreach (Task t in this.tasks.GetConsumingEnumerable())
                                {
                                    this.TryExecuteTask(t);
                                }
                            }) {
                                  Name = "Sta Thread", IsBackground = true 
                               };
                    thread.SetApartmentState(ApartmentState.STA);
                    return thread;
                }).ToList();

        // Start all of the threads
        this.threads.ForEach(t => t.Start());
    }

    /// <summary>
    ///     Gets the maximum concurrency level supported by this scheduler.
    /// </summary>
    public override int MaximumConcurrencyLevel
    {
        get
        {
            return this.threads.Count;
        }
    }

    /// <summary>
    ///     Cleans up the scheduler by indicating that no more tasks will be queued.
    ///     This method blocks until all threads successfully shutdown.
    /// </summary>
    public void Dispose()
    {
        if (this.tasks != null)
        {
            // Indicate that no new tasks will be coming in
            this.tasks.CompleteAdding();

            // Wait for all threads to finish processing tasks
            foreach (Thread thread in this.threads)
            {
                thread.Join();
            }

            // Cleanup
            this.tasks.Dispose();
            this.tasks = null;
        }
    }

    /// <summary>
    ///     Provides a list of the scheduled tasks for the debugger to consume.
    /// </summary>
    /// <returns>An enumerable of all tasks currently scheduled.</returns>
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        // Serialize the contents of the blocking collection of tasks for the debugger
        return this.tasks.ToArray();
    }

    /// <summary>
    ///     Queues a Task to be executed by this scheduler.
    /// </summary>
    /// <param name = "task">The task to be executed.</param>
    protected override void QueueTask(Task task)
    {
        // Push it into the blocking collection of tasks
        this.tasks.Add(task);
    }

    /// <summary>
    ///     Determines whether a Task may be inlined.
    /// </summary>
    /// <param name = "task">The task to be executed.</param>
    /// <param name = "taskWasPreviouslyQueued">Whether the task was previously queued.</param>
    /// <returns>true if the task was successfully inlined; otherwise, false.</returns>
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        // Try to inline if the current thread is STA
        return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA && this.TryExecuteTask(task);
    }
}
于 2011-06-07T18:00:08.443 回答
1

也许您应该使用任务并行库?

我是 TPL 的初学者,但有些调度程序可能有一些选项可以在计划任务上设置 STAThread。

于 2011-05-18T22:26:23.407 回答