为什么.net单元测试要覆盖并行.Foreach循环依赖于硬件

本文关键字:Foreach 循环 依赖于 硬件 并行 覆盖 net 单元测试 为什么 | 更新日期: 2023-09-27 18:16:56

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

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

这通过我的i7处理器,我检查了代码。

之后,一个同事抱怨说那不适合他。结果是Parallel.foreach在他的Core2duo上只产生了2个线程,因此只有2个异常被包裹在AggregateException中。

问题是该怎么做,所以单元测试不依赖于处理器架构?几点思考:-

  1. 有一篇关于手动添加异常的微软文章AggregateException,但我们不热衷于这样做的循环出现问题应及时退出。
  2. ParallelOptions.MaxDegreeOfParallelism可以对所使用的线程数量设置一个上限。但除非被拒绝到1(这看起来更像是作弊而不是适当的单元测试)如何单元测试能知道实际使用了多少线程吗因此正确设置ArrangeAssert阶段?

为什么.net单元测试要覆盖并行.Foreach循环依赖于硬件

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

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

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

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

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

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

如果您期望在处理过程中报告所有潜在的错误,那么您的同事的运行实际上发现了您代码中的错误。这意味着普通的Parallel.ForEach()不适合您,但是像Parallel.ForEach()这样的try - catch和自定义异常处理可以。

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

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

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