7

我正在使用 Moq 对一些包含Parallel.foreach循环的代码进行单元测试。

Arrange阶段设置了 4 个要在循环中抛出的异常,然后包装在AggregateException.

这通过了我的 i7 处理器,我签入了代码。

之后,一位同事抱怨这对他来说并没有通过。结果Parallel.foreach在轰炸之前只在他的 Core2duo 上产生了 2 个线程,因此只有 2 个异常被包裹在AggregateException.

问题是如何处理这个单元测试不依赖于处理器架构?几个想法:-

  1. 有一篇关于手动添加异常的Microsoft 文章AggregateException,但我们并不热衷于这样做,因为如果出现问题,循环应该尽早退出。
  2. ParallelOptions.MaxDegreeOfParallelism可以对使用的线程数设置一个上限。但是除非这被拒绝为 1(这似乎更像是作弊而不是正确的单元测试)单元测试如何知道实际将使用多少线程并因此正确设置ArrangeAssert阶段?
4

2 回答 2

6

你不应该测试这样的东西——它是实现细节。

事情是 -Parallel.ForEach将处理元素,直到它出现异常。当发生异常时,它将停止处理任何新元素(但将完成对当前处理的元素的处理)然后 throw AgregateException

现在 - 您的 i7 CPU 有 4 个内核 + 超线程,这会导致产生更多线程进行处理,因此您可以获得更多异常(例如,发生异常时可以同时处理 4 件事)。但是在只有 2 个内核的 Core2Duo 上,只会同时处理 2 个项目(这是因为 TPL 足够聪明,只能创建足够多的线程进行处理,而不是可用内核)。

实际发生 4 个异常的测试不会给您任何知识。它取决于机器。相反,您应该测试是否至少发生了一个异常 - 因为这是您所期望的。如果未来的用户将在旧的单核机器上运行您的代码,他将在AggregateException.

抛出异常的数量是特定于机器的,例如计算时间 - 你不会断言计算了多长时间,所以在这种情况下你不应该断言异常的数量。

于 2013-06-05T11:27:21.573 回答
1

单元测试验证您期望发生的事情和实际发生的事情是同一件事。这意味着您需要问自己您期望发生什么。

如果你期望处理中的所有潜在错误都会被报告,那么你的同事的运行实际上发现了你的代码中的一个错误。这意味着 plainParallel.ForEach()对你不起作用,但是像Parallel.ForEach()-trycatch自定义异常处理这样的东西会。

如果您期望每个抛出的异常都会被捕获,那么这就是您需要测试的。这可能会使您的测试复杂化,或者可能无法测试,具体取决于您如何抛出异常。对此的简单测试如下所示:

[Test]
public void ParallelForEachExceptionsTest()
{
    var exceptions = new ConcurrentQueue<Exception>();
    var thrown = Assert.Throws<AggregateException>(
        () => Parallel.ForEach(
            Enumerable.Range(1, 4), i =>
            {
                var e = new Exception(i.ToString());
                exceptions.Enqueue(e);
                throw e;
            }));

    CollectionAssert.AreEquivalent(exceptions, thrown.InnerExceptions);
}

如果你期望的是当一个或多个异常被抛出时,那么至少其中一些会被捕获,那么这就是你应该测试的,如果所有的异常都被正确捕获,你也不用担心。

于 2013-06-05T12:45:17.217 回答