如何在C#中对单元测试性能优化进行单元测试?

问题描述 投票:23回答:1

我在我正在构建的一些搜索代码中使用了Levenshtein算法的优化版本。我有功能单元测试来验证算法返回正确的结果,但在这种情况下,算法的性能也非常重要。

我希望为项目添加一些测试覆盖率,以便如果将来的任何修改影响优化,它们将显示为失败测试 - 因为算法是确定性的并且针对已知测试数据运行,这可能与计数一样详细为给定的一组测试输入执行的指令数。换句话说,我不打算使用定时器测量算法性能 - 我对实际测试算法的内部行为而不仅仅是输出感兴趣。

有什么想法我会如何在C#/ .NET 4中解决这个问题?

编辑:我不想只使用挂钟时间的原因是它会随着CPU负载和测试控制之外的其他因素而变化。例如,这可能导致在构建服务器负载时测试失败。作为部署系统的一部分,将有挂钟监控。

编辑2:这样想吧......当性能是关键要求时,你会如何应用red-> green-> refactor?

c# unit-testing optimization micro-optimization
1个回答
31
投票

我将回答你问题的第三部分,因为我已经多次取得了一些成功。

当性能是关键要求时,你会如何应用red-> green-> refactor?

  1. 编写固定测试以捕获回归,计划更改的内容以及可能因更改而变慢的其他方法。
  2. 编写失败的性能测试。
  3. 提高性能,经常运行所有测试。
  4. 更新固定测试以更紧密地确定性能。

写钉扎测试

创建一个这样的辅助方法来计算你想要的东西。

private TimeSpan Time(Action toTime)
{
    var timer = Stopwatch.StartNew();
    toTime();
    timer.Stop();
    return timer.Elapsed;
}

然后编写一个测试,断言你的方法不花时间:

[Test]
public void FooPerformance_Pin()
{
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0));
}

当它失败时(失败消息中已经过了实际时间),用稍微超过实际时间的东西更新时间。重新运行,它将通过。对可能影响您的更改的性能的其他函数重复此操作,最后得到类似的结果。

[Test]
public void FooPerformance_Pin()
{
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0.8));
}
[Test]
public void BarPerformance_Pin()
{
    Assert.That(Time(()=>fooer.Bar()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(6));
}

写一个失败的性能测试

我喜欢称这种测试为“诱饵测试”。这只是钉扎测试的第一步。

[Test]
public void FooPerformance_Bait()
{
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0));
}

现在,开展性能改进。在每次试验性改进之后运行所有测试(钉扎和诱饵)。如果您成功,您将看到诱饵测试的失败输出中的时间下降,并且您的任何钉扎测试都不会失败。

如果您对这些改进感到满意,请更新您更改的代码的固定测试,并删除诱饵测试。

你现在对这些测试做了什么?

最不令人担忧的事情是使用Explicit属性标记这些测试,并在下次要检查性能时保留它们。

在工作范围的另一端,在CI中创建一个控制合理的子系统来运行这些测试是监控性能回归的一种非常好的方法。根据我的经验,有很多人担心它们“由于来自其他方面的CPU负载而随机失败”而不是实际失败。这种努力的成功更多地取决于团队文化,而不是你对环境进行控制的能力。

© www.soinside.com 2019 - 2024. All rights reserved.