27

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

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

任何想法我将如何在 C#/.NET 4 中解决这个问题?

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

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

4

1 回答 1

34

我将回答你问题的第三部分,因为我已经多次成功地做到了这一点。

当性能是关键要求时,您将如何应用 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));
}

现在,致力于性能改进。在每次尝试性改进后运行所有测试(固定和诱饵)。如果您成功了,您会在 baiting 测试的失败输出中看到时间减少,并且您的所有 pinning 测试都不会失败。

当您对改进感到满意时,为您更改的代码更新 pinning 测试,并删除 baiting 测试。

您现在如何处理这些测试?

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

在工作范围的另一边,在 CI 中创建一个控制良好的子系统来运行这些测试是监控性能回归的一个非常好的方法。以我的经验,与实际故障相比,人们更担心它们“由于其他原因导致 CPU 负载随机故障”。这种努力的成功更多地取决于团队文化,而不是你对环境的控制能力。

于 2013-03-03T03:15:38.223 回答